Skip to content

Commit

Permalink
Merge pull request #36 from superhawk610/wip
Browse files Browse the repository at this point in the history
v1.1 Milestone
  • Loading branch information
superhawk610 authored Dec 30, 2017
2 parents 718b70a + 0320da0 commit 9c8386a
Show file tree
Hide file tree
Showing 33 changed files with 1,816 additions and 583 deletions.
13 changes: 13 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
language: node_js
node_js:
- node
cache:
directories:
- node_modules
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y build-essential libavahi-compat-libdnssd-dev
before_script:
- npm install -g gulp-cli
script: gulp
script: npm test
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ MultiCast v1.0
=========

[![npm version](https://badge.fury.io/js/multicast.svg)](https://badge.fury.io/js/multicast)
[![Build Status](https://travis-ci.org/superhawk610/multicast.svg?branch=wip)](https://travis-ci.org/superhawk610/multicast)

:green_heart: A persistent solution to presenting content across multiple Chromecast devices, inspired by [Greenscreen](http://greenscreen.io/).

Expand Down
3 changes: 2 additions & 1 deletion app/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ var startConfig = () => {
console.log('')
console.log('Configuration updated! To get started, just run\n' +
'\n' +
' multicast start')
' multicast start'
'\n')
})
rl.close()
} else {
Expand Down
44 changes: 44 additions & 0 deletions app/lib/channels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict'

const Channel = require('../models/Channel')

const devices = require('./devices')

let channels = []

const func = {
init: () => func.refresh(),
refresh: () => {
Channel.find()
.sort('name')
.exec((err, _channels) => {
if (err) console.log(err)
channels = _channels
})
},
list: () => channels,
withId: id => channels.find(c => c._id == id),
create: (opts, callback) => {
var c = new Channel(opts)
c.save((err, channel) => {
if (err) console.log(err)
channels.push(c)
callback(channel._id)
})
},
update: (id, opts, callback) => {
Channel.update({ _id: id }, opts, err => {
Object.assign(func.withId(id), opts)
opts._id = id
devices.updateChannel(opts, () => callback(id))
})
},
remove: (id, callback) => {
Channel.remove({ _id: id }, () => {
channels.splice(channels.findIndex(c => c._id == id), 1) // remove from local listing
devices.removeChannel(id, () => callback())
})
}
}

module.exports = func
15 changes: 11 additions & 4 deletions app/lib/config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use strict'

const fs = require('fs')
const path = require('path')

Expand All @@ -11,20 +13,25 @@ const configVariables = [
'mongoPort'
]

try {
const port = 3944

try {
// Attempts to pull entire config from environment variables. If any key is
// not found, the configuration will instead be drawn from the .config file
// which should be placed in the project root.
var envUsed = true
configVariables.forEach(envVar => {
if (process.env[envVar] != undefined) module.exports[envVar] = process.env[envVar]
if (process.env[envVar] != undefined)
module.exports[envVar] = process.env[envVar]
else envUsed = false // If any are empty, the .config file will be used
})

// Skips if the full config is already in the environment variables
if (!envUsed) module.exports = JSON.parse(fs.readFileSync(configPath))

if (!envUsed) {
let opts = JSON.parse(fs.readFileSync(configPath))
opts.port = port
module.exports = opts
}
} catch (e) {
console.log(`No config file found in ${configPath}
Please run
Expand Down
114 changes: 114 additions & 0 deletions app/lib/connection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
'use strict'

const Client = require('castv2').Client
const config = require('./config')

let func = {
establish: (d, disconnectCallback) => {
let host = d.address
d.connectionFailCount = 0
if (d.status == 'offline' || d.status == 'waiting') {
const client = new Client()
client.connect(host, () => {
// reset number of failed connection attempts
d.connectionFailCount = 0

// create various namespace handlers
d.connection = client.createChannel(
'sender-0',
'receiver-0',
'urn:x-cast:com.google.cast.tp.connection',
'JSON'
)
d.heartbeat = client.createChannel(
'sender-0',
'receiver-0',
'urn:x-cast:com.google.cast.tp.heartbeat',
'JSON'
)
d.receiver = client.createChannel(
'sender-0',
'receiver-0',
'urn:x-cast:com.google.cast.receiver',
'JSON'
)

// establish virtual connection to the receiver
d.connection.send({ type: 'CONNECT' })

// start heartbeating
d.missedHeartbeats = 0
d.pulse = setInterval(() => {
d.missedHeartbeats++
if (d.missedHeartbeats > 6) {
// receiver has been offline for more than 30 seconds
d.status = 'offline' // mark receiver as offline
clearInterval(d.pulse) // stop checking for pulse :(
func.addError(
d,
'Receiver missed too many heartbeats, it is likely offline.'
)
} else d.heartbeat.send({ type: 'PING' })
}, 5 * 1000)
d.heartbeat.on('message', (data, broadcast) => {
if (data.type == 'PONG') {
d.missedHeartbeats = 0
d.status = 'online'
func.clearErrors(d)
}
})

// launch hub app
d.receiver.send({ type: 'LAUNCH', appId: config.appId, requestId: 1 })

// monitor receiver status updates to insure hub is open
d.receiver.on('message', (data, broadcast) => {
if (data.type != 'RECEIVER_STATUS')
func.addError(d, `Message from receiver: ${data.type}`)
if ((data.type = 'RECEIVER_STATUS')) {
// data.status contains relevant information about current app, volume, etc
if (data.status && data.status.applications) {
var apps = data.status.applications

/* Backdrop means that our hub applications has stopped running, so we need to restart it */
if (apps.find(a => a.displayName == 'Backdrop')) {
func.addError(d, 'Receiver closed hub, relaunching...')
d.receiver.send({
type: 'LAUNCH',
appId: config.appId,
requestId: 1
})
}
}
}
})
})
client.on('error', () => {
d.connectionFailCount++
if (d.connectionFailCount > 6) {
// receiver hasn't responded after 60 seconds
clearTimeout(d.connectionFail)
func.addError(
d,
'Receiver is unresponsive, attempting to reconnect...'
)
} else d.connectionFail = setTimeout(disconnectCallback, 10 * 1000)
})
}
},
hasErrors: () => Object.keys(errors).length > 0,
getErrors: () => errors,
addError: (device, message) => {
if (!errors[device.deviceId])
errors[device.deviceId] = {
name: device.location,
messages: []
}
if (errors[device.deviceId].messages.indexOf(message) == -1)
errors[device.deviceId].messages.push(message)
},
clearErrors: device => delete errors[device.deviceId]
},
errors = {}

module.exports = func
22 changes: 16 additions & 6 deletions app/lib/dbConnect.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
'use strict'

const mongoose = require('mongoose')

module.exports = config => {
const userPassword = config.mongoUser ? `${config.mongoUser}:${config.mongoPass}@` : ''
mongoose.connect(
`mongodb://${userPassword}${config.mongoHost}:${config.mongoPort}/multicast?authSource=${config.mongoAuthSource}`, {
useMongoClient: true
}).on('error', () => console.log('Could not connect to Mongo.'))
}
const userPassword = config.mongoUser
? `${config.mongoUser}:${config.mongoPass}@`
: ''
mongoose
.connect(
`mongodb://${userPassword}${config.mongoHost}:${
config.mongoPort
}/multicast?authSource=${config.mongoAuthSource}`,
{
useMongoClient: true
}
)
.on('error', () => console.log('Could not connect to Mongo.'))
}
Loading

0 comments on commit 9c8386a

Please sign in to comment.