diff --git a/embed/core.js b/embed/core.js index 11463278..d8aaeabf 100644 --- a/embed/core.js +++ b/embed/core.js @@ -544,7 +544,54 @@ MistPlayer.prototype.buildMistControls = function(){ return true; } - +MistPlayer.prototype.askNextCombo = function(){ + var me = this; + me.errorstate = true; + + var err = document.createElement('div'); + var msgnode = document.createTextNode('Player or stream error detected'); + err.appendChild(msgnode); + err.className = 'error'; + var button = document.createElement('button'); + var t = document.createTextNode('Try next source/player'); + button.appendChild(t); + err.appendChild(button); + button.onclick = function(){ + me.nextCombo(); + } + var button = document.createElement('button'); + var t = document.createTextNode('Reload this player'); + button.appendChild(t); + err.appendChild(button); + button.onclick = function(){ + me.reload(); + } + err.style.position = 'absolute'; + err.style.top = 0; + err.style.width = '100%'; + err.style['margin-left'] = 0; + + this.target.appendChild(err); + this.element.style.opacity = '0.2'; +}; +MistPlayer.prototype.cancelAskNextCombo = function(){ + if (this.errorstate) { + this.element.style.opacity = 1; + var err = this.target.querySelector('.error'); + if (err) { + this.target.removeChild(err); + } + this.errorstate = false; + } +}; +MistPlayer.prototype.reload = function(){ + mistPlay(this.mistplaySettings.streamname,this.mistplaySettings.options); +}; +MistPlayer.prototype.nextCombo = function(){ + var opts = this.mistplaySettings.options; + opts.startCombo = this.mistplaySettings.startCombo; + mistPlay(this.mistplaySettings.streamname,opts); +}; ///////////////////////////////////////////////// // SELECT AND ADD A VIDEO PLAYER TO THE TARGET // @@ -557,7 +604,8 @@ function mistPlay(streamName,options) { protoplay.sendEvent('log',msg,options.target); } function mistError(msg) { - var info = mistvideo[streamName]; + var info = {}; + if ((typeof mistvideo != 'undefined') && ('streamName' in mistvideo)) { info = mistvideo[streamName]; } var displaymsg = msg; if ('on_error' in info) { displaymsg = info.on_error; } @@ -570,6 +618,7 @@ function mistPlay(streamName,options) { err.appendChild(button); button.onclick = function(){ options.target.removeChild(err); + delete options.startCombo; mistPlay(streamName,options); } @@ -625,10 +674,12 @@ function mistPlay(streamName,options) { embedLog('Retrieving stream info from '+info.src); document.head.appendChild(info); info.onerror = function(){ + options.target.innerHTML = ''; options.target.removeAttribute('data-loading'); mistError('Error while loading stream info.'); } info.onload = function(){ + options.target.innerHTML = ''; options.target.removeAttribute('data-loading'); embedLog('Stream info was loaded succesfully'); @@ -640,6 +691,11 @@ function mistPlay(streamName,options) { //embedLog('Stream info contents: '+JSON.stringify(streaminfo)); streaminfo.initTime = new Date(); + if (!('source' in streaminfo)) { + mistError('Error while loading stream info.'); + return; + } + //sort the sources by priority and mime, but prefer HTTPS streaminfo.source.sort(function(a,b){ return (b.priority - a.priority) || a.type.localeCompare(b.type) || b.url.localeCompare(a.url); @@ -673,6 +729,15 @@ function mistPlay(streamName,options) { embedLog('The forced player ('+options.forcePlayer+') isn\'t known, ignoring. Possible values are: '+Object.keys(mistplayers).join(', ')); } } + var startCombo = false; + if ('startCombo' in options) { + startCombo = options.startCombo; + startCombo.started = { + player: false, + source: false + }; + embedLog('Selecting a new player/source combo, starting after '+mistplayers[startCombo.player].name+' with '+streaminfo.source[startCombo.source].type+' @ '+streaminfo.source[startCombo.source].url); + } embedLog('Checking available players..'); @@ -680,6 +745,12 @@ function mistPlay(streamName,options) { var mistPlayer = false; function checkPlayer(p_shortname) { + if ((startCombo) && (!startCombo.started.player)) { + if (p_shortname != startCombo.player) { return false; } + else { + startCombo.started.player = true; + } + } embedLog('Checking '+mistplayers[p_shortname].name+' (priority: '+mistplayers[p_shortname].priority+') ..'); streaminfo.working[p_shortname] = []; @@ -706,22 +777,39 @@ function mistPlay(streamName,options) { else { loop = streaminfo.source; } + var broadcast = false; for (var s in loop) { if (loop[s].type == mime) { - if (mistplayers[p_shortname].isBrowserSupported(mime,loop[s],options)) { + broadcast = true; + + if ((startCombo) && (!startCombo.started.source)) { + if (s == startCombo.source) { + startCombo.started.source = true; + } + continue; + } + + if (mistplayers[p_shortname].isBrowserSupported(mime,loop[s],options,streaminfo)) { embedLog('Found a working combo: '+mistplayers[p_shortname].name+' with '+mime+' @ '+loop[s].url); streaminfo.working[p_shortname].push(mime); if (!source) { mistPlayer = p_shortname; source = loop[s]; + source.index = s; } if (!forceSupportCheck) { return source; } } + else { + embedLog('This browser does not support '+mime); + } } + + } + if (!broadcast) { + embedLog('Mist doesn\'t broadcast '+mime); } - embedLog('Mist doesn\'t broadcast '+mime+' or there is no browser support.'); return false; } @@ -742,7 +830,6 @@ function mistPlay(streamName,options) { } } - options.target.innerHTML = ''; if (mistPlayer) { //create the options to send to the player var playerOpts = { @@ -870,8 +957,25 @@ function mistPlay(streamName,options) { } //build the player + player.mistplaySettings = { + streamname: streamName, + options: local, + startCombo: { + player: mistPlayer, + source: source.index + } + }; player.options = playerOpts; - var element = player.build(playerOpts); + try { + var element = player.build(playerOpts); + } + catch (e) { + //show the next player/reload buttons if there is an error in the player build code + options.target.appendChild(player.element); + player.askNextCombo(); + throw e; + return; + } options.target.appendChild(element); element.setAttribute('data-player',mistPlayer); element.setAttribute('data-mime',source.type); @@ -884,7 +988,28 @@ function mistPlay(streamName,options) { } //monitor for errors - //TODO + player.checkPlayingTimeout = false; + element.addEventListener('error',function(e){ + player.askNextCombo(); + },true); + var stalled = function(e){ + if (player.checkPlayingTimeout) { return; } + player.checkPlayingTimeout = setTimeout(function(){ + if (player.element.readyState >= 2) { return; } + player.askNextCombo(); + },5e3); + }; + element.addEventListener('stalled',stalled,true); + element.addEventListener('waiting',stalled,true); + var progress = function(e){ + if (player.checkPlayingTimeout) { + clearTimeout(player.checkPlayingTimeout); + player.checkPlayingTimeout = false; + player.cancelAskNextCombo(); + } + }; + element.addEventListener('progress',progress,true); + element.addEventListener('playing',progress,true); if (player.resize) { //monitor for resizes and fire if needed diff --git a/embed/mist.css b/embed/mist.css index f08a1e86..f123c353 100644 --- a/embed/mist.css +++ b/embed/mist.css @@ -9,6 +9,8 @@ color: white; font-family: sans-serif; text-align: center; + position: relative; + text-shadow: 0 0 1px black, 0 0 1px black; } .mistvideo[data-loading] { background-image: none; @@ -35,11 +37,16 @@ } .mistvideo .error { margin: 225px 20px 20px; + min-width: 300px; + z-index: 69; } .mistvideo .error button { margin: 5px auto; display: block; } +.mistvideo .vjs-error-display:before { + content: '' !important; +} .mistplayer { position: relative; overflow: hidden; diff --git a/embed/test.html b/embed/test.html index 0f198561..8fc95132 100644 --- a/embed/test.html +++ b/embed/test.html @@ -15,9 +15,9 @@ @@ -76,9 +76,10 @@ //tryplayers = Object.keys(mistplayers); tryplayers = []; + tryplayers.push('derp'); //tryplayers.push('html5'); //tryplayers.push('dashjs'); - tryplayers.push('videojs'); + //tryplayers.push('videojs'); //tryplayers.push('flash_strobe'); //tryplayers.push('silverlight'); streams = []; @@ -86,8 +87,9 @@ //streams.push('subtel'); //streams.push('ogg'); //streams.push('vids+mist.mp4'); + streams.push('vids+hahalol.mp3'); //streams.push('lama'); - streams.push('bunny'); + //streams.push('bunny'); for (var j in streams) { for (var i in tryplayers) { @@ -100,7 +102,7 @@ maxwidth: 800, forcePlayer: tryplayers[i], //forceType: 'html5/video/mp4', - forceType: 'html5/application/vnd.apple.mpegurl', + //forceType: 'html5/application/vnd.apple.mpegurl', //forceType: 'dash/video/mp4', //forceSource: 5, loop: true, diff --git a/embed/wrappers/html5.js b/embed/wrappers/html5.js index 4b39287e..e7193ea4 100644 --- a/embed/wrappers/html5.js +++ b/embed/wrappers/html5.js @@ -5,11 +5,18 @@ mistplayers.html5 = { isMimeSupported: function (mimetype) { return (this.mimes.indexOf(mimetype) == -1 ? false : true); }, - isBrowserSupported: function (mimetype) { + isBrowserSupported: function (mimetype,source,options,streaminfo) { if ((['iPad','iPhone','iPod','MacIntel'].indexOf(navigator.platform) != -1) && (mimetype == 'html5/video/mp4')) { return false; } + var support = false; var shortmime = mimetype.split('/'); shortmime.shift(); + + if ((shortmime[0] == 'audio') && (streaminfo.height)) { + //claim you don't support audio only playback if there is video data + return false; + } + try { var v = document.createElement((shortmime[0] == 'audio' ? 'audio' : 'video')); shortmime = shortmime.join('/') @@ -35,9 +42,9 @@ p.prototype.build = function (options) { var ele = this.element((shortmime[0] == 'audio' ? 'audio' : 'video')); ele.className = ''; cont.appendChild(ele); - //ele.crossOrigin = 'anonymous'; + ele.crossOrigin = 'anonymous'; //required for subtitles if (shortmime[0] == 'audio') { - this.setTracks = false; + this.setTracks = function() { return false; } this.fullscreen = false; cont.className += ' audio'; } @@ -92,51 +99,10 @@ p.prototype.build = function (options) { ele.addEventListener('error',function(e){ if ((ele.error) && (ele.error.code == 3)) { ele.load(); + me.cancelAskNextCombo(); me.addlog('Decoding error: reloading..'); } },true); - - var errorstate = false; - function dced(e) { - if (errorstate) { return; } - - errorstate = true; - me.adderror('Connection lost..'); - - var err = document.createElement('div'); - var msgnode = document.createTextNode('Connection lost..'); - err.appendChild(msgnode); - err.className = 'error'; - var button = document.createElement('button'); - var t = document.createTextNode('Reload'); - button.appendChild(t); - err.appendChild(button); - button.onclick = function(){ - errorstate = false; - ele.parentNode.removeChild(err); - ele.load(); - ele.style.opacity = ''; - } - err.style.position = 'absolute'; - err.style.top = 0; - err.style.width = '100%'; - err.style['margin-left'] = 0; - - ele.parentNode.appendChild(err); - ele.style.opacity = '0.2'; - - function nolongerdced(){ - ele.removeEventListener('progress',nolongerdced); - errorstate = false; - ele.parentNode.removeChild(err); - ele.style.opacity = ''; - } - ele.addEventListener('progress',nolongerdced); - } - - ele.addEventListener('stalled',dced,true); - ele.addEventListener('ended',dced,true); - ele.addEventListener('pause',dced,true); } this.addlog('Built html'); diff --git a/embed/wrappers/videojs.js b/embed/wrappers/videojs.js index 664aed22..5bf34816 100644 --- a/embed/wrappers/videojs.js +++ b/embed/wrappers/videojs.js @@ -25,6 +25,7 @@ p.prototype.build = function (options) { var ele = this.element('video'); cont.appendChild(ele); ele.className = ''; + ele.crossOrigin = 'anonymous'; //required for subtitles var shortmime = options.source.type.split('/'); shortmime.shift();