symple-client-player-fixed
Version:
Symple realtime media player - Fixed
245 lines (211 loc) • 7.12 kB
JavaScript
//
// Symple.MJPEG.js
// MJPEG Engine for the Symple
//
// Copyright (c)2010 Sourcey
// http://sourcey.com
// Distributed under The MIT License.
//
(function (S) {
// Native MJPEG Engine
//
// - Works in Firefox, Chrome and Safari except iOS >= 6.
//
S.Media.register({
id: 'mjpeg:native',
name: 'MJPEG Native',
formats: 'MJPEG',
preference: 60,
defaults: {
framing: 'multipart'
},
support: (function () {
var ua = navigator.userAgent
var iOS = S.iOSVersion()
return !!(ua.match(/(Firefox|Chrome)/) ||
// iOS < 6 or desktop safari
(iOS ? iOS < 6 : ua.match(/(Safari)/)))
})()
})
S.Player.BrowserCompatabilityMsg = '\
<br>Download the latest version <a href="www.google.com/chrome/">Chrome</a> or \
<a href="http://www.apple.com/safari/">Safari</a> to view this video stream.'
S.Player.MJPEG = S.Player.extend({
init: function (player) {
this._super(player)
this.img = null
},
play: function (params) {
params = params || {};
params.framing = 'multipart'; // using multipart/x-mixed-replace
S.log('symple:mjpeg:native: Play', params)
if (this.img) { throw 'Streaming already initialized' }
this._super(params)
// TODO: Some kind of connection timeout
// this.params = params;
// this.params.url = this.buildURL();
// if (!this.params.url)
// throw 'Invalid streaming URL'
var self = this
var init = true
this.img = new Image()
// this.img.style.width = '100%'; // constraints set on screen element
// this.img.style.height = '100%';
this.img.style.display = 'none'
this.img.onload = function () {
S.log('symple:mjpeg:native: Success')
// Most browsers inclusing WebKit just call onload once.
if (init) {
if (self.img) { self.img.style.display = 'inline' }
self.setState('playing')
init = false
}
// Some browsers, like Firefox calls onload on each
// multipart segment, so we can display status.
else { self.displayFPS() }
}
// NOTE: This never fires in latest chrome
// when the remote side disconnects stream.
this.img.onerror = function () {
self.setError('Streaming connection failed.' + S.Player.BrowserCompatabilityMsg)
}
this.img.src = params.url // + "&rand=" + Math.random();
this.screen.appendChild(this.img)
},
stop: function () {
S.log('symple:mjpeg:native: Stop')
this.cleanup()
this.setState('stopped')
},
cleanup: function () {
if (this.img) {
this.img.style.display = 'none'
this.img.src = '#' // closes the socket in ff, but not webkit
this.img.onload = new Function()
this.img.onerror = new Function()
this.screen.removeChild(this.img)
this.img = null
}
},
setError: function (error) {
S.log('Symple MJPEG Engine: Error:', error)
this.cleanup()
this.setState('error', error)
}
})
// MJPEG WebSocket Engine
//
// Requires HyBi binary WebSocket support.
// Available in all the latest browsers:
// http://en.wikipedia.org/wiki/WebSocket
//
window.WebSocket = window.WebSocket || window.MozWebSocket
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL
S.Media.register({
id: 'mjpeg:ws',
name: 'MJPEG WebSocket',
formats: 'MJPEG',
preference: 50,
support: (function () {
return !!(window.WebSocket && window.WebSocket.CLOSING === 2 && window.URL)
})()
})
S.Player.MJPEGWebSocket = S.Player.extend({
init: function (player) {
this._super(player)
this.socket = null
this.img = null
},
play: function (params) {
if (this.active()) { throw 'Streaming already active' }
params = params || {};
params.framing = 'multipart'; // using multipart/x-mixed-replace
this._super(params)
this.createImage()
var self = this,
init = true,
url = this.normalizeURL(params.url)
S.log('symple:mjpeg:ws: play:', url)
this.socket = new WebSocket(url)
this.socket.onopen = function () {
S.log('symple:mjpeg:ws: open')
// self.socket.send('ping');
}
this.socket.onmessage = function (e) {
S.log('symple:mjpeg:ws: message: ', e)
// http://www.adobe.com/devnet/html5/articles/real-time-data-exchange-in-html5-with-websockets.html
// http://stackoverflow.com/questions/15040126/receiving-websocket-arraybuffer-data-in-the-browser-receiving-string-instead
// http://stackoverflow.com/questions/9546437/how-send-arraybuffer-as-binary-via-websocket/11426037#11426037
if (!self.active()) {
self.setError('Streaming failed')
}
if (init) {
self.setState('playing')
init = false
}
// TODO: Image content type
S.log('symple:mjpeg:ws: frame', self, e.data)
var blob = window.URL.createObjectURL(e.data)
self.img.onload = function () {
window.URL.revokeObjectURL(blob)
}
self.img.src = blob
// self.displayFPS()
}
this.socket.onerror = function (error) {
// Invalid MJPEG streams will end up here
S.log('symple:mjpeg:ws: onerror', error)
self.setError('Invalid MJPEG stream: ' + error + '.')
}
},
stop: function () {
S.log('symple:mjpeg:ws: stop')
this.cleanup()
this.setState('stopped')
},
active: function (params) {
return this.img !== null && this.socket !== null
},
cleanup: function () {
S.log('symple:mjpeg:ws: cleanup')
if (this.img) {
this.img.style.display = 'none'
this.img.src = '#' // XXX: Closes socket in ff, but not safari
this.img.onload = null
this.img.onerror = null
this.screen.removeChild(this.img)
this.img = null
}
if (this.socket) {
S.log('symple:mjpeg:ws: cleanup: socket: ', this.socket)
// BUG: Not closing in latest chrome,
this.socket.close()
this.socket = null
}
},
createImage: function () {
if (!this.img) {
this.img = new Image()
this.img.style.width = '100%'
this.img.style.height = '100%'
// We will end up here if the MJPEG stream is invalid.
// NOTE: This never fires in latest chrome when the
// remote side disconnects stream.
var self = this
this.img.onerror = function (e) {
S.log('symple:mjpeg:ws: image load error: ', e)
self.setError('Invalid MJPEG stream');
}
this.screen.appendChild(this.img)
}
},
normalizeURL: function (url) {
return url.replace(/^http/, 'ws')
},
setError: function (error) {
S.log('symple:mjpeg:ws: error:', error)
this.cleanup()
this.setState('error', error)
}
})
})(window.Symple = window.Symple || {})