Created
May 13, 2010 01:13
-
-
Save jsjohnst/399364 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
@description Patch file to allow widgets using the KONtx.mediaplayer API to run on any Framework version 1.2.20 or higher on 2009 and 2010 model TVs | |
@author jstone | |
*/ | |
KONtx_automation_log("loadjs","mediaplayer.js"); | |
if(!KONtx.mediaplayer || !KONtx.mediaplayer._subscribers || typeof KONtx.mediaplayer._subscribers.onSystemPathCreationFailure !== "object") { | |
log("");log("**");log(""); | |
log("Loading KONtx.mediaplayer patch for < FW 1.3.27"); | |
log("");log("**");log(""); | |
KONtx.mediaplayer = function() { | |
var internals = {}; | |
internals.subscribeableEvents = [ | |
'onRemoteKeyPress', | |
'onPauseRemoteKeyPress', | |
'onStopRemoteKeyPress', | |
'onPlayRemoteKeyPress', | |
'onRewindRemoteKeyPress', | |
'onFastForwardRemoteKeyPress', | |
'onStateChange', | |
'onPlaybackBuffering', | |
'onSetScreensaverMode', | |
'onTimeIndexChanged', | |
'onResetBufferingCount', | |
'onConnectionBandwidthChanged', | |
'onViewportBoundsChanged', | |
'onSetPlaybackSpeed', | |
'onControlPlay', | |
'onControlPause', | |
'onControlStop', | |
'onControlRewind', | |
'onControlFastForward', | |
'onControlSeek', | |
'onControlStreamSwitch', | |
'onConvertToSpeed', | |
'onFindBestStream', | |
'onStartStreamPlayback', | |
'onStreamLoadError', | |
'onPlayPlaylistEntry', | |
'onProcessPlaylistEntry', | |
'onPlaylistChange', | |
'onStartPlaylist', | |
'onLoadPlaylistEntry', | |
'onPlaylistRepeat', | |
'onPlaylistEnd', | |
'onLoadPreviousPlaylistEntry', | |
'onLoadNextPlaylistEntry', | |
'onNewStreamSelected', | |
'onSystemPathCreationFailure' | |
]; | |
internals.constants = {}; | |
/* | |
* Psuedo enum of all the remote transport keys | |
*/ | |
internals.constants.keys = { | |
PAUSE : 19, | |
STOP : 413, | |
REWIND : 412, | |
PLAY : 415, | |
FASTFORWARD : 417 | |
}; | |
/* | |
* Pseudo Enum of all the possible states we will return on the onStateChange event | |
*/ | |
internals.constants.states = { | |
INIT : -1, | |
PLAY : 0, | |
PAUSE : 1, | |
FASTFORWARD : 2, | |
FORWARD : 2, | |
FF : 2, | |
REWIND : 3, | |
STOP : 4, | |
BUFFERING : 5, | |
BUFFEREMPTY: 6, | |
INFOLOADED: 7, | |
EOF: 8, | |
UNKNOWN : 9, | |
ERROR : 10 | |
}; | |
/* | |
* Pseudo Enum of all the allowed stream switch methods | |
*/ | |
internals.constants.streamswitch = { | |
INDEX_CHANGE : 1, | |
BANDWIDTH : 2 | |
}; | |
internals.active_states = [ | |
internals.constants.states.PLAY, | |
internals.constants.states.PAUSE, | |
internals.constants.states.FASTFORWARD, | |
internals.constants.states.REWIND, | |
internals.constants.states.BUFFERING, | |
internals.constants.states.INFOLOADED | |
]; | |
internals.eventListeners = {}; | |
internals.tvapi = { | |
control: null, | |
path: null, | |
input: null, | |
output: null, | |
state: internals.constants.states.INIT, | |
timeIndex: null, | |
mediaDuration: null | |
}; | |
internals.media = { | |
playlist: null, | |
playlist_index: null, | |
currentEntry: null, | |
stream_index: null, | |
stream_count: null, | |
buffering_count: -1 | |
}; | |
internals.bandwidth = { | |
bitrate: 0, | |
margin: 1 | |
}; | |
/* | |
* Object to handle various path creation related pointers | |
*/ | |
internals.path_handling = { | |
timer: null, | |
playWhenReady: false, | |
playWhenReadyTimeIndex: 0 | |
}; | |
internals.init = function(config) { | |
config = config || {}; | |
KONtx.HostEventManager.send("setExitVideoDialog", false); | |
if(!internals.tvapi.control || !internals.tvapi.path || !internals.tvapi.path.isValid() || !internals.tvapi.input || !internals.tvapi.input.isValid()) { | |
tv.onPathCreated = internals.handlePathCreated.bindTo(internals); | |
internals.tvapi.output = tv.getOutput( 'VideoPlaybackScreen' ) || tv.getOutput( 'PrimaryScreen' ); | |
} | |
// The below logic is used to check if config.bindKeyHandler === false. If it is, then don't bind the key handler | |
if(!('bindKeyHandler' in config && config.bindKeyHandler === false) && !internals.boundPlayControlKeyHandler) { | |
internals.log("Binding Remote Key Handler"); | |
internals.boundPlayControlKeyHandler = internals.playControlKeyHandler.subscribeTo(KONtx.application, 'onPlayControlKeyPress', internals); | |
} else { | |
internals.log("Skipping binding Remote Key Handler"); | |
} | |
}; | |
internals.destroyTVAPIPointers = function() { | |
internals.log("Destroying TV API pointers"); | |
if(internals.tvapi.control) { | |
internals.log("Removing callbacks on TVControl instance and nulling out"); | |
internals.tvapi.control.onStateChanged = null; | |
internals.tvapi.control.onTimeIndexChanged = null; | |
internals.tvapi.control = null; | |
} | |
if(internals.tvapi.path && internals.tvapi.path.isValid()) { | |
internals.log("Destroying TVPath"); | |
tv.destroyPath( internals.tvapi.path.getID() ); | |
internals.tvapi.path = null; | |
} | |
if(internals.tvapi.input && internals.tvapi.input.isValid()) { | |
internals.log("Destroying TVNetworkInput"); | |
tv.destroyNetworkInput( internals.tvapi.input ); | |
} | |
}; | |
// start of path creation | |
internals.createAndLinkNewInput = function() { | |
internals.log("Creating new input and linking"); | |
internals.destroyTVAPIPointers(); | |
internals.tvapi.input = tv.createNetworkInput( '' ); | |
var path = tv.createPath(internals.tvapi.input, internals.tvapi.output); | |
// TVPath might not be a defined class so we have to be extra careful on 2009 TVs | |
if (!(path === true || path === false) && path instanceof TVPath) { | |
internals.log("We have a TVPath now, so must be on >2009 TV"); | |
internals.tvapi.path = path; | |
internals.setupTVAPIControl(); | |
} else { | |
internals.log("We didn't get a TVPath back, so wait until the callback"); | |
if(!internals.path_handling.timer) { | |
internals.log("Creating a timer in case we never get a onPathCreated callback"); | |
internals.path_handling.timer = new Timer(); | |
internals.path_handling.timer.interval = 2; | |
internals.path_handling.timer.firstPass = true; | |
internals.path_handling.timer.onTimerFired = function () { | |
if (internals.path_handling.timer.firstPass) { | |
internals.log("First firing of the timer and still no path, this isn't good"); | |
internals.path_handling.timer.firstPass = false; | |
internals.path_handling.timer.interval = 30; | |
} else { | |
internals.log("Complete failure. Giving up waiting on a path creation."); | |
if (internals.path_handling.timer) { | |
internals.path_handling.timer.ticking = false; | |
internals.path_handling.timer = null; | |
} | |
if (this.fire("onStreamLoadError") && this.fire("onSystemPathCreationFailure")) { | |
internals.log("Neither failure event was canceled, so doing a little house keeping"); | |
internals.destroyTVAPIPointers(); | |
} | |
} | |
}; | |
internals.path_handling.timer.ticking = true; | |
} else { | |
internals.log("We already have an existing timer"); | |
} | |
} | |
}; | |
internals.playControlKeyHandler = function(event) { | |
if(internals.fire("onRemoteKeyPress", { keyCode: event.payload.keyCode })) { | |
switch(event.payload.keyCode) { | |
case internals.constants.keys.PAUSE: | |
internals.log("pause remote key pressed"); | |
if(internals.fire("onPauseRemoteKeyPress", { keyCode: event.payload.keyCode })) { | |
internals.pause(); | |
} | |
break; | |
case internals.constants.keys.STOP: | |
internals.log("stop remote key pressed"); | |
if(internals.fire("onStopRemoteKeyPress", { keyCode: event.payload.keyCode })) { | |
internals.stop(); | |
} | |
break; | |
case internals.constants.keys.PLAY: | |
internals.log("play remote key pressed"); | |
if(internals.fire("onPlayRemoteKeyPress", { keyCode: event.payload.keyCode })) { | |
internals.play(); | |
} | |
break; | |
case internals.constants.keys.REWIND: | |
internals.log("rewind remote key pressed"); | |
if(internals.fire("onRewindRemoteKeyPress", { keyCode: event.payload.keyCode })) { | |
internals.log("Rewind is only supported in limited circumstances"); | |
internals.log("So if you want it handled, you need to provide your own handler"); | |
} | |
break; | |
case internals.constants.keys.FASTFORWARD: | |
internals.log("fastforward remote key pressed"); | |
if(internals.fire("onFastForwardRemoteKeyPress", { keyCode: event.payload.keyCode })) { | |
internals.log("Fastforward is only supported in limited circumstances"); | |
internals.log("So if you want it handled, you need to provide your own handler"); | |
} | |
break; | |
default: | |
log("We don't know what to do for keyCode: ", event.payload.keyCode); | |
break; | |
} | |
} | |
}; | |
// Handling broken TV API in 5.5 engines by executing the callback inside a timer which will execute the callback after the current execution scope completes | |
internals.handleBrokenTVAPIWithDelay = function(callback) { | |
internals.log("Delaying execution of callback until the current execution scope completes"); | |
internals.log($dump(callback)); | |
var timer = new Timer(); | |
timer.onTimerFired = function() { | |
this.ticking = false; | |
log("Timer delay has elapsed, now calling callback"); | |
callback(); | |
} | |
timer.interval = 0.1; | |
timer.ticking = true; | |
}; | |
internals.delayStartNextClip = function() { | |
internals.log("Delaying start of next clip by 1/2 second."); | |
var callback = internals.next_playlist_entry.bindTo(internals); | |
internals.handleBrokenTVAPIWithDelay(callback); | |
}; | |
internals.handleStateChange = function(newstate) { | |
var previousState = internals.tvapi.state; | |
internals.tvapi.state = newstate; | |
internals.log("State Change from " + previousState + " to " + newstate); | |
if (internals.fire("onStateChange", { newState: newstate, previousState: previousState })) { | |
if(newstate == internals.constants.states.BUFFERING) { | |
internals.media.buffering_count++; | |
if(internals.fire("onPlaybackBuffering")) { | |
KONtx.utility.WaitIndicator.on(); | |
} | |
} else { | |
KONtx.utility.WaitIndicator.off(); | |
} | |
if(newstate == internals.constants.states.EOF) { | |
if(internals.media.playlist_index + 1 < internals.media.playlist.entries.length) { | |
// if we will be for sure playing more clips, add a slight delay between clips to let low level player recover | |
internals.delayStartNextClip(); | |
} else { | |
// otherwise, just let the normal logic handle it without a delay | |
internals.next_playlist_entry(); | |
} | |
} | |
if(newstate == internals.constants.states.PAUSE || newstate == internals.constants.states.STOP) { | |
if(internals.fire("onSetScreensaverMode", { mode: "on" })) { | |
KONtx.HostEventManager.send("setScreensaver", true); | |
} | |
} else { | |
if(internals.fire("onSetScreensaverMode", { mode: "off" })) { | |
KONtx.HostEventManager.send("setScreensaver", false); | |
} | |
} | |
} | |
}; | |
internals.handleTimeIndexChange = function() { | |
internals.log("Time index changed: ", internals.tvapi.control.timeIndex); | |
internals.tvapi.timeIndex = internals.tvapi.control.timeIndex; | |
internals.tvapi.mediaDuration = internals.tvapi.control.duration; | |
internals.fire("onTimeIndexChanged", { timeIndex: Math.floor(internals.tvapi.control.timeIndex / 1000), | |
duration: Math.floor(internals.tvapi.control.duration/1000), | |
rawTimeIndex: internals.tvapi.control.timeIndex, | |
rawDuration: internals.tvapi.control.duration }); | |
}; | |
internals.delayStartPlaybackIfNeeded = function() { | |
internals.log("Checking if we need to start playback now that we have a path"); | |
if (internals.path_handling.playWhenReady) { | |
internals.log("We were asked to do a delayed start play, so call play_stream()"); | |
var startIndex = internals.path_handling.playWhenReadyTimeIndex; | |
internals.path_handling.playWhenReadyTimeIndex = 0; | |
internals.play_stream(startIndex); | |
} | |
}; | |
internals.setupTVAPIControl = function() { | |
internals.log("Fetching TVControl from the path"); | |
internals.tvapi.control = internals.tvapi.path.control; | |
if(internals.tvapi.control) { | |
internals.log("We've got a TVControl, so bind the handlers"); | |
internals.tvapi.control.onStateChanged = internals.handleStateChange.bindTo(internals); | |
internals.tvapi.control.onTimeIndexChanged = internals.handleTimeIndexChange.bindTo(internals); | |
var callback = internals.delayStartPlaybackIfNeeded.bindTo(this); | |
internals.handleBrokenTVAPIWithDelay(callback); | |
} else { | |
internals.log_error("Unable to retrieve TVPathControl for pathID: ", pathID); | |
} | |
}; | |
// callback from tv.createPath | |
// async on 2009 | |
internals.handlePathCreated = function(pathID) { | |
internals.log("Got a onPathCreated callback!"); | |
if (internals.path_handling.timer) { | |
internals.log("Disabling the timer and nulling it out"); | |
internals.path_handling.timer.ticking = false; | |
internals.path_handling.timer = null; | |
} | |
if(!internals.tvapi.path || !internals.tvapi.path.isValid() || internals.tvapi.path.getID() != pathID) { | |
internals.log("We don't already have a path, so using this new one"); | |
internals.tvapi.path = tv.getPath(pathID); | |
if(internals.tvapi.path && internals.tvapi.path.isValid()) { | |
internals.log("Path is valid, so now setting up TVControl"); | |
internals.setupTVAPIControl(); | |
} else { | |
internals.log_error("Unable to retrieve TVPath for pathID: ", pathID); | |
} | |
} else { | |
internals.log("We already have a valid path, so ignoring this path creation"); | |
} | |
}; | |
internals.log_error = function(msg) { | |
log("");log("");log(""); | |
log(msg); | |
log("");log("");log(""); | |
return false; | |
}; | |
// can be set to a no-op later potentially | |
internals.log = log; | |
internals.makeEventPayload = function(payload) { | |
internals.log("Creating event payload with additional payload of: ", $dump(payload, 3)); | |
payload = payload || {}; | |
payload.player = {}; | |
payload.player.media = internals.media; | |
payload.player.tvapi = internals.tvapi; | |
payload.player.bandwidth = internals.bandwidth; | |
payload.player.states = internals.constants.states; | |
payload.player.keys = internals.constants.keys; | |
return payload; | |
}; | |
// this is slightly different than the normal fire | |
internals.fire = function(eventType, eventPayload) { | |
var event; | |
if (eventType instanceof KONtx.system.Event) { | |
event = eventType; | |
eventType = event.type; | |
internals.log("firing event of type:" + eventType); | |
} else { | |
internals.log("firing event of type:" + eventType); | |
eventPayload = internals.makeEventPayload(eventPayload); | |
event = new KONtx.system.Event(eventType, eventPayload); | |
} | |
// filter the array of subscribers of this type - remove invalid entries | |
internals.eventListeners[eventType] = [].concat(internals.eventListeners[eventType]).filter(function(sub){ | |
return sub instanceof Function; | |
}); | |
if (!internals.eventListeners[eventType].length) { | |
internals.log("no listeners for event"); | |
return true; // don't delete the key! it's a getter/setter! | |
} | |
var defaultPrevented = false; | |
for(var i in internals.eventListeners[event.type]) { | |
try { | |
internals.eventListeners[event.type][i](event); | |
} catch(e) { | |
log("Caught an exception from subscriber. Removing from subscribers list.", $dump(e, 3)); | |
// array entry will get cleaned up by above filter next time, so just null it out | |
internals.eventListeners[event.type][i] = null; | |
} | |
if(event.defaultPrevented) { | |
internals.log("event default prevented"); | |
defaultPrevented = true; | |
} | |
if(event.propagationStopped) { | |
internals.log("event propagation stopped"); | |
break; | |
} | |
} | |
return !defaultPrevented; | |
}; | |
internals.resetBufferingCount = function() { | |
internals.log("reset buffering count called"); | |
if(internals.fire("onResetBufferingCount", { bufferingCount: internals.media.buffering_count })) { | |
internals.log("resetting buffering count"); | |
internals.media.buffering_count = -1; | |
} | |
}; | |
internals.setConnectionBandwidth = function(bitrate, margin) { | |
internals.log("Call to setConnectionBandwidth with values: ", bitrate, " / ", margin); | |
var previousBitrate = internals.bandwidth.bitrate; | |
var previousMargin = internals.bandwidth.margin; | |
internals.log("Previous bandwith values: ", previousBitrate, " / ", previousMargin); | |
internals.bandwidth.bitrate = bitrate; | |
margin = Number(margin); | |
if(margin > 0 && margin <= 1) { | |
internals.bandwidth.margin = margin; | |
} | |
internals.log("Player connection bandwidth set to: ", internals.bandwidth.bitrate, " with margin: ", internals.bandwidth.margin); | |
internals.fire("onConnectionBandwidthChanged", { bandwidth: { previous: { bitrate: previousBitrate, margin: previousMargin }, | |
current: { bitrate: internals.bandwidth.bitrate, margin: internals.bandwidth.margin } } }); | |
}; | |
internals.getDefaultViewportBounds = function() { | |
if(internals.tvapi.output && internals.tvapi.output.nativeBounds && internals.tvapi.output.nativeBounds instanceof Rectangle) { | |
log("TV API gave us bounds: ", $dump(internals.tvapi.output.nativeBounds, 2)); | |
var bounds = internals.tvapi.output.nativeBounds; | |
return { x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height }; | |
} else { | |
log("We can't get bounds from the TV API, so assume 1920x1080"); | |
// we have no way to get this, so assume 1920x1080 | |
return { x: 0, y: 0, width: 1920, height: 1080 }; | |
} | |
}; | |
internals.scaleViewportBounds = function(x, y, width, height) { | |
log("Scaling providing 960x540 offsets to native offsets"); | |
var defaultBounds = internals.getDefaultViewportBounds(); | |
var x_scaler = defaultBounds.width / 960; | |
var y_Scaler = defaultBounds.height / 540; | |
log("X scaler: ", x_scaler); | |
log("Y scaler: ", y_scaler); | |
x = Math.round(x * x_scaler); | |
y = Math.round(y * y_scaler); | |
width = Math.round(width * x_scaler); | |
height = Math.round(height * y_scaler); | |
internals.setViewportBounds(x, y, width, height); | |
}; | |
internals.setViewportBounds = function(x, y, width, height) { | |
if(!internals.tvapi.output) { | |
internals.log_error("No tvapi output to set viewport bounds on!"); | |
return; | |
} | |
if($type(x) == "object" && x.x >= 0 && x.y >= 0 && x.width > 0 && x.height > 0) { | |
internals.log("We got a viewport dimensions object as the first param, parsing it."); | |
y = x.y; | |
width = x.width; | |
height = x.height; | |
x = x.x; | |
} | |
internals.log("Changing viewport bounds to (x/y/w/h): ", x, "/", y, "/", width, "/", height); | |
var previousBounds = internals.tvapi.output.bounds; | |
internals.log("Previous viewport bounds were (x/y/w/h): ", previousBounds.x, "/", previousBounds.y, "/", previousBounds.width, "/", previousBounds.height); | |
internals.tvapi.output.bounds = new Rectangle(x, y, width, height); | |
internals.fire("onViewportBoundsChanged", { viewport: { previous: previousBounds, current: internals.tvapi.output.bounds } }); | |
}; | |
internals.setPlaybackSpeed = function(speed) { | |
if(!internals.tvapi.control) { | |
internals.log_error("No tvapi control to set speed on!"); | |
return; | |
} | |
internals.log("Changing playback speed from: ", internals.tvapi.control.speed, " to: ", speed); | |
if(internals.fire("onSetPlaybackSpeed", { speed: { previous: internals.tvapi.control.speed, current: speed } })) { | |
internals.tvapi.control.speed = speed; | |
} | |
}; | |
internals.play = function() { | |
if(!internals.tvapi.control || !internals.tvapi.control.play) { | |
internals.log_error("No tvapi control to call play on!"); | |
return; | |
} | |
if(!(internals.media.playlist_index >= 0 && internals.media.stream_index >= 0)) { | |
internals.log("apparently someone pressed play before calling start on the playlist, so handle it for them!"); | |
internals.start_playlist(); | |
return; | |
} | |
internals.log("request to begin media playback"); | |
if(internals.fire("onControlPlay")) { | |
internals.resetBufferingCount(); | |
internals.setPlaybackSpeed(1); | |
internals.log("calling play() on TVPathControl object"); | |
internals.tvapi.control.play(); | |
} | |
}; | |
internals.pause = function() { | |
if(!internals.tvapi.control || !internals.tvapi.control.pause) { | |
internals.log_error("No tvapi control to call pause on!"); | |
return; | |
} | |
internals.log("request to pause media playback"); | |
if(internals.fire("onControlPause")) { | |
internals.resetBufferingCount(); | |
internals.log("calling pause() on TVPathControl object"); | |
internals.tvapi.control.pause(); | |
} | |
}; | |
internals.stop = function() { | |
if(!internals.tvapi.control || !internals.tvapi.control.stop) { | |
internals.log_error("No tvapi control to call stop on!"); | |
return; | |
} | |
internals.log("request to stop media playback"); | |
if(internals.fire("onControlStop")) { | |
internals.resetBufferingCount(); | |
internals.log("calling stop() on TVPathControl object"); | |
internals.tvapi.control.stop(); | |
} | |
}; | |
internals.convertToSpeed = function(increment) { | |
var speed = 1; | |
increment = Math.abs(increment); | |
switch(true) { | |
case increment === 0: | |
speed = 1; | |
break; | |
case isNaN(increment): // intentionally falling through | |
case increment <= 2: | |
speed = 2; | |
break; | |
case increment <= 4: | |
speed = 4; | |
break; | |
case increment <= 8: | |
speed = 8; | |
break; | |
case increment > 8: | |
speed = 16; | |
break; | |
default: | |
speed = 2; | |
break; | |
} | |
internals.log("Converted increment: ", increment, " to speed: ", speed); | |
internals.fire("onConvertToSpeed", { increment: increment, speed: speed }); | |
return speed; | |
}; | |
internals.rewind = function(increment) { | |
internals.log("request to rewind media playback with increment: ", increment); | |
if(internals.fire("onControlRewind", { increment: increment })) { | |
internals.resetBufferingCount(); | |
internals.setPlaybackSpeed(internals.convertToSpeed(increment) * -1); | |
} | |
}; | |
internals.fastforward = function(increment) { | |
internals.log("request to fastforward media playback with increment: ", increment); | |
if(internals.fire("onControlFastForward", { increment: increment })) { | |
internals.resetBufferingCount(); | |
internals.setPlaybackSpeed(internals.convertToSpeed(increment)); | |
} | |
}; | |
internals.seek = function(offset, absolute) { | |
if(!internals.tvapi.control) { | |
internals.log_error("No tvapi control to set time index on!"); | |
return; | |
} | |
internals.log("request to seek media playback to offset: ", offset, absolute ? "" : " relative to current position"); | |
if(internals.fire("onControlSeek", { offset: offset, absolute: absolute })) { | |
internals.resetBufferingCount(); | |
if(absolute) { | |
offset = Math.abs(offset); | |
internals.log("seeking stream to position: ", offset, " seconds"); | |
internals.tvapi.control.timeIndex = Number(offset) * 1000; | |
} else { | |
internals.log("seeking stream ", offset, " seconds from current position"); | |
internals.tvapi.control.timeIndex += Number(offset) * 1000; | |
} | |
} | |
}; | |
internals.streamswitch = function(method, config) { | |
internals.log("request to stream switch"); | |
if(internals.fire("onControlStreamSwitch", { streamswitch: { method: method, config: config } })) { | |
switch(method) { | |
case internals.constants.streamswitch.INDEX_CHANGE: | |
var offset_amount = Number(config.offset_amount); | |
if(config.direction && (config.direction == "up" || config.direction == "down")) { | |
// if they didn't provide an offset amount, then default to 1 | |
// if using direction, you normally wouldn't provide an offset | |
// unless you wanted to jump down 2 for example | |
if(!offset_amount) { | |
internals.log("no offset provided, so default to 1"); | |
offset_amount = 1; | |
} | |
// the array is sorted from highest to lowest | |
// so to reduce bandwidth, our offset amount must increase | |
// to increase bitrate, our offset must decrease | |
if(config.direction == "down") { | |
internals.log("direction was down, so positive increment index"); | |
offset_amount = Math.abs(offset_amount); | |
} else { | |
internals.log("direction was up, so negatively increment index"); | |
offset_amount = Math.abs(offset_amount) * -1; | |
} | |
} | |
internals.log("stream switching by changing stream offset by: ", offset_amount); | |
var previous_stream_index = internals.media.stream_index; | |
internals.media.stream_index = Number(internals.media.stream_index) + offset_amount; | |
if(internals.media.stream_index >= internals.media.currentEntry.streams.length) { | |
internals.media.stream_index = internals.media.currentEntry.streams.length - 1; | |
} | |
// intentionally no else if, so we always make sure we are positive here | |
if(internals.media.stream_index < 0) { | |
internals.media.stream_index = 0; | |
} | |
internals.log("stream switching from previous stream index: ", previous_stream_index, " to new index of: ", internals.media.stream_index); | |
internals.fire("onNewStreamSelected"); | |
break; | |
case internals.constants.streamswitch.BANDWIDTH: | |
internals.log("stream switching by setting new bandwidth bitrate: ", config.bitrate, " and margin: ", config.margin); | |
internals.setConnectionBandwidth(config.bitrate, config.margin); | |
internals.find_best_stream(); | |
break; | |
default: | |
throw new Error("Can't switch stream without knowing what method to use for the switch"); | |
break; | |
} | |
internals.log("now resuming play on new stream at existing time index"); | |
internals.resetBufferingCount(); | |
internals.play_stream(internals.tvapi.control.timeIndex); | |
} | |
}; | |
internals.find_best_stream = function() { | |
if(!internals.media.currentEntry) { | |
internals.log_error("Can't find a stream to play without a valid entry!"); | |
return; | |
} | |
if(internals.fire("onFindBestStream")) { | |
var index = 0; | |
internals.media.stream_count = internals.media.currentEntry.streams.length; | |
internals.log("Connection Bitrate: ", internals.bandwidth.bitrate, " Margin: ", internals.bandwidth.margin); | |
for(index in internals.media.currentEntry.streams) { | |
if(Number(internals.media.currentEntry.streams[index].bitrate) === 0) { | |
internals.log("Selecting stream index: ", index, " which has no bitrate and has URL: ", internals.media.currentEntry.streams[index].url); | |
internals.media.stream_index = index; | |
internals.fire("onNewStreamSelected"); | |
return; | |
} | |
if(Number(internals.media.currentEntry.streams[index].bitrate) < (internals.bandwidth.bitrate * internals.bandwidth.margin)) { | |
internals.log("Selecting stream index: ", index, " which has bitrate: ", internals.media.currentEntry.streams[index].bitrate, " and URL: ", internals.media.currentEntry.streams[index].url); | |
internals.media.stream_index = index; | |
internals.fire("onNewStreamSelected"); | |
return; | |
} | |
internals.log("Skipping stream index: ", index, " which has bitrate: ", internals.media.currentEntry.streams[index].bitrate, " and URL: ", internals.media.currentEntry.streams[index].url); | |
} | |
internals.log("Index: ", index); | |
if(internals.media.playlist.forcePlay) { | |
internals.media.stream_index = index; | |
internals.log("Forcing playback on stream index: ", index, " which has bitrate: ", internals.media.currentEntry.streams[index].bitrate, " and URL: ", internals.media.currentEntry.streams[index].url); | |
internals.fire("onNewStreamSelected"); | |
} else { | |
internals.media.stream_index = null; | |
internals.log_error("We found no stream to play!"); | |
} | |
} | |
}; | |
// end of path | |
internals.play_stream = function(startIndex) { | |
internals.log("Attempting to play stream at time index:", startIndex); | |
internals.path_handling.playWhenReady = false; | |
if(internals.media.stream_index !== null && internals.media.stream_index >= 0 && internals.media.stream_index < internals.media.currentEntry.streams.length) { | |
if(!internals.tvapi.path || !internals.tvapi.path.isValid() || !internals.tvapi.control) { | |
internals.log("Have Path: ", internals.tvapi.path ? "true" : "false"); | |
internals.log("Path Valid: ", internals.tvapi.path && internals.tvapi.path.isValid() ? "true" : "false"); | |
internals.log("Have Control: ", internals.tvapi.control ? "true" : "false"); | |
// set flag to play on path ready | |
internals.log("Setting flag requesting delayed start of playback when path is created"); | |
internals.path_handling.playWhenReady = true; | |
internals.path_handling.playWhenReadyTimeIndex = startIndex; | |
return; | |
} | |
var url = internals.media.currentEntry.streams[internals.media.stream_index].url; | |
if(internals.fire("onStartStreamPlayback", { selectedURL: url, callbackHandler: internals.play_stream_handler.bindTo(internals), startIndex: startIndex })) { | |
internals.log("Calling play_stream_handler"); | |
internals.play_stream_handler(url, startIndex); | |
} | |
} else { | |
internals.log("Stream load error!"); | |
internals.fire("onStreamLoadError"); | |
} | |
}; | |
internals.play_stream_handler = function(url, startIndex) { | |
internals.log("setting networkinput to url: ", url); | |
internals.tvapi.input.uri = url; | |
if(startIndex) { | |
internals.log("starting media stream at index: ", startIndex); | |
internals.tvapi.control.timeIndex = startIndex; | |
} | |
internals.log("starting stream playback"); | |
internals.tvapi.control.play(); | |
}; | |
internals.check_streams_ready_then_play = function() { | |
if(!internals.media.currentEntry.streamsReady(internals.check_streams_ready_then_play.bindTo(internals))) { | |
internals.log("Streams aren't ready yet. We will wait for the callback from the playlist entry indicating they are ready."); | |
return; | |
} | |
internals.find_best_stream(); | |
internals.createAndLinkNewInput(); | |
if(internals.fire("onPlayPlaylistEntry")) { | |
internals.play_stream(internals.media.currentEntry.startIndex * 1000); | |
} | |
}; | |
internals.play_entry = function(entry) { | |
internals.log("fixing to play new playlist entry"); | |
if(internals.fire("onProcessPlaylistEntry", { entry: entry })) { | |
internals.media.currentEntry = entry; | |
internals.media.stream_index = null; | |
internals.check_streams_ready_then_play(); | |
} | |
}; | |
internals.playlist_getter = function() { | |
internals.log("playlist get() was called"); | |
return internals.media.playlist; | |
}; | |
internals.playlist_setter = function(playlist) { | |
internals.log("playlist set() was called"); | |
if(playlist instanceof KONtx.media.Playlist) { | |
if(internals.fire("onPlaylistChange", { playlist: playlist })) { | |
internals.media.playlist = playlist; | |
} | |
} else { | |
throw new Error("Playlists must be an instance of (or inherit from) KONtx.media.Playlist"); | |
} | |
}; | |
internals.start_playlist = function() { | |
internals.log("playlist start() was called"); | |
if(internals.fire("onStartPlaylist")) { | |
if(internals.player_active()) { | |
internals.log_error("you didn't call stop() before trying to start a new playlist. I am doing it for you now, but this might have unintended side effects"); | |
internals.stop(); | |
} | |
internals.load_playlist_entry(0); | |
} | |
}; | |
internals.load_playlist_entry = function(index) { | |
if(!internals.media.playlist || !internals.media.playlist.entries.length) { | |
internals.log_error("Can't load a playlist entry without having a playlist!"); | |
return; | |
} | |
if(index < 0) index = 0; | |
internals.log("Fixing to load playlist entry at index: ", index); | |
if(internals.fire("onLoadPlaylistEntry", { index: index })) { | |
if( internals.media.playlist.entries[index] instanceof KONtx.media.PlaylistEntry ) { | |
internals.media.playlist_index = index; | |
internals.play_entry(internals.media.playlist.entries[index]); | |
} else if( internals.media.playlist.entries.length && internals.media.playlist.entries.length < index && internals.media.playlist.repeatAll ) { | |
// we check length first above to not get in an infinite loop from an empty list | |
if(internals.fire("onPlaylistRepeat")) { | |
internals.load_playlist_entry(0); | |
} | |
} else { | |
internals.fire("onPlaylistEnd"); | |
} | |
} | |
}; | |
internals.previous_playlist_entry = function() { | |
internals.log("loading previous playlist entry"); | |
if(internals.fire("onLoadPreviousPlaylistEntry")) { | |
internals.load_playlist_entry(internals.media.playlist_index - 1); | |
} | |
}; | |
internals.next_playlist_entry = function() { | |
internals.log("loading next playlist entry"); | |
if(internals.fire("onLoadNextPlaylistEntry")) { | |
internals.load_playlist_entry(internals.media.playlist_index + 1); | |
} | |
}; | |
internals.player_active = function() { | |
return internals.media.currentEntry && $contains(internals.tvapi.state, internals.active_states); | |
}; | |
internals.subscriberProxy = {}; | |
internals.subscribeableEvents.forEach(function(eventType) { | |
internals.subscriberProxy.__defineGetter__(eventType, function() { | |
return internals.eventListeners[eventType] ? internals.eventListeners[eventType] : []; | |
}); | |
internals.subscriberProxy.__defineSetter__(eventType, function(data) { | |
if(data instanceof Array) { | |
internals.eventListeners[eventType] = data; | |
} else { | |
throw new Error("KONtx.mediaplayer.subscribe: Invalid setting of subscribers for eventType: ", eventType, "\n", $dump(data)); | |
} | |
}); | |
}); | |
internals.controlsProxy = {}; | |
internals.controlsProxy.__defineGetter__('play', function() { | |
return internals.play; | |
}); | |
internals.controlsProxy.__defineGetter__('pause', function() { | |
return internals.pause; | |
}); | |
internals.controlsProxy.__defineGetter__('stop', function() { | |
return internals.stop; | |
}); | |
internals.controlsProxy.__defineGetter__('rewind', function() { | |
return internals.rewind; | |
}); | |
internals.controlsProxy.__defineGetter__('fastforward', function() { | |
return internals.fastforward; | |
}); | |
internals.controlsProxy.__defineGetter__('seek', function() { | |
return internals.seek; | |
}); | |
internals.controlsProxy.__defineGetter__('streamswitch', function() { | |
return internals.streamswitch; | |
}); | |
internals.playlistProxy = {}; | |
internals.playlistProxy.__defineGetter__('get', function() { | |
return internals.playlist_getter; | |
}); | |
internals.playlistProxy.__defineGetter__('set', function() { | |
return internals.playlist_setter; | |
}); | |
internals.playlistProxy.__defineGetter__('start', function() { | |
return internals.start_playlist; | |
}); | |
internals.playlistProxy.__defineGetter__('loadEntry', function() { | |
return internals.load_playlist_entry; | |
}); | |
internals.playlistProxy.__defineGetter__('previousEntry', function() { | |
return internals.previous_playlist_entry; | |
}); | |
internals.playlistProxy.__defineGetter__('nextEntry', function() { | |
return internals.next_playlist_entry; | |
}); | |
internals.playlistProxy.__defineGetter__('currentEntry', function() { | |
return internals.media.currentEntry; | |
}); | |
internals.playlistProxy.__defineGetter__('currentIndex', function() { | |
return { entry: internals.media.playlist_index, stream: internals.media.stream_index }; | |
}); | |
internals.tvAPIProxy = {}; | |
internals.tvAPIProxy.__defineGetter__('activePath', function() { | |
return internals.tvapi.path; | |
}); | |
internals.tvAPIProxy.__defineGetter__('activeControl', function() { | |
return internals.tvapi.control; | |
}); | |
internals.tvAPIProxy.__defineGetter__('activeInput', function() { | |
return internals.tvapi.input; | |
}); | |
internals.tvAPIProxy.__defineGetter__('activeOutput', function() { | |
return internals.tvapi.output; | |
}); | |
internals.tvAPIProxy.__defineGetter__('currentPlayerState', function() { | |
return internals.tvapi.state; | |
}); | |
internals.tvAPIProxy.__defineGetter__('currentTimeIndex', function() { | |
return Number(internals.tvapi.timeIndex); | |
}); | |
internals.tvAPIProxy.__defineGetter__('currentMediaDuration', function() { | |
return Number(internals.tvapi.mediaDuration); | |
}); | |
internals.tvAPIProxy.__defineGetter__('currentHTTPErrorCode', function() { | |
return internals.tvapi.input ? internals.tvapi.input.httpErrorCode : -1; | |
}); | |
internals.constantsProxy = {}; | |
internals.constantsProxy.__defineGetter__('states', function() { | |
return internals.constants.states; | |
}); | |
internals.constantsProxy.__defineGetter__('keys', function() { | |
return internals.constants.keys; | |
}); | |
internals.constantsProxy.__defineGetter__('streamswitch', function() { | |
return internals.constants.streamswitch; | |
}); | |
internals.publicAPIProxy = {}; | |
internals.publicAPIProxy.__defineGetter__('_subscribers', function() { | |
return internals.subscriberProxy; | |
}); | |
internals.publicAPIProxy.__defineGetter__('control', function() { | |
return internals.controlsProxy; | |
}); | |
internals.publicAPIProxy.__defineGetter__('tvapi', function() { | |
return internals.tvAPIProxy; | |
}); | |
internals.publicAPIProxy.__defineGetter__('playlist', function() { | |
return internals.playlistProxy; | |
}); | |
internals.publicAPIProxy.__defineGetter__('constants', function() { | |
return internals.constantsProxy; | |
}); | |
internals.publicAPIProxy.__defineGetter__('initialize', function() { | |
return internals.init; | |
}); | |
internals.publicAPIProxy.__defineGetter__('setConnectionBandwidth', function() { | |
return internals.setConnectionBandwidth; | |
}); | |
internals.publicAPIProxy.__defineGetter__('setViewportBounds', function() { | |
return internals.setViewportBounds; | |
}); | |
internals.publicAPIProxy.__defineGetter__('scaleViewportBounds', function() { | |
return internals.scaleViewportBounds; | |
}); | |
internals.publicAPIProxy.__defineGetter__('getDefaultViewportBounds', function() { | |
return internals.getDefaultViewportBounds; | |
}); | |
internals.publicAPIProxy.__defineGetter__('isPlaylistEntryActive', function() { | |
return internals.player_active(); | |
}); | |
internals.publicAPIProxy.__defineGetter__('debugInternals', function() { | |
log("");log("ATTENTION QA!!");log(""); | |
log("");log("ATTENTION QA!!");log(""); | |
log("Access into private internals of KONtx.mediaplayer is for debugging purposes only. This should never happen in live code!!"); | |
log("");log("ATTENTION QA!!");log(""); | |
log("");log("ATTENTION QA!!");log(""); | |
log("");log(""); | |
return internals; | |
}); | |
return internals.publicAPIProxy; | |
}(); | |
KONtx.mediaplayer.version = 2867; // disable patch loading if using our provided patch | |
KONtx.media = {}; | |
KONtx.media.Playlist = new KONtx.Class({ | |
config: { | |
autoStart: false, | |
repeatAll: false, | |
forcePlay: true | |
}, | |
initialize: function() { | |
this._playlist = {}; | |
this._playlist.entries = []; | |
this._playlist.autoStart = Boolean(this.config.autoStart); | |
this._playlist.repeatAll = Boolean(this.config.repeatAll); | |
this._playlist.forcePlay = Boolean(this.config.forcePlay); | |
this.__defineGetter__('autoStart', function() { | |
return this._playlist.autoStart; | |
}); | |
this.__defineSetter__('autoStart', function(value) { | |
this._playlist.autoStart = Boolean(value); | |
}); | |
this.__defineGetter__('repeatAll', function() { | |
return this._playlist.repeatAll; | |
}); | |
this.__defineSetter__('repeatAll', function(value) { | |
this._playlist.repeatAll = Boolean(value); | |
}); | |
this.__defineGetter__('forcePlay', function() { | |
return this._playlist.forcePlay; | |
}); | |
this.__defineSetter__('forcePlay', function(value) { | |
this._playlist.forcePlay = Boolean(value); | |
}); | |
this._isFiltered = false; | |
// intentionally no setter here, use helpers below | |
this.__defineGetter__('entries', this._entriesGetter); | |
}, | |
_entriesGetter: function() { | |
if(!this._isFiltered) { | |
// make sure the list is valid first | |
this._playlist.entries = [].concat(this._playlist.entries).filter(function(entry){ | |
return entry instanceof KONtx.media.PlaylistEntry; | |
}); | |
this._isFiltered = true; | |
} | |
return this._playlist.entries; | |
}, | |
// important to set _isFiltered flag to false if adding any additional setters which add/remove from _playlist | |
removeEntry: function(index) { | |
this._isFiltered = false; | |
this._playlist.entries[index] = null; | |
}, | |
clearEntries: function() { | |
this._isFiltered = false; | |
this._playlist.entries = []; | |
}, | |
addEntry: function(entry) { | |
this._isFiltered = false; | |
this.addEntries([entry]); | |
return this; | |
}, | |
addEntries: function(entries) { | |
this._isFiltered = false; | |
this._playlist.entries = this._playlist.entries.concat(entries); | |
return this; | |
}, | |
addEntryByURL: function(url, bitrate, startIndex) { | |
this._isFiltered = false; | |
var entry = new KONtx.media.PlaylistEntry({ | |
url: url, | |
bitrate: bitrate, | |
startIndex: startIndex | |
}); | |
this.addEntries([entry]); | |
return this; | |
} | |
}); | |
KONtx.media.PlaylistEntry = new KONtx.Class({ | |
config: { | |
url: null, | |
bitrate: null, | |
startIndex: null, | |
streams: null | |
}, | |
initialize: function() { | |
this._startIndex = this.config.startIndex; | |
this.__defineGetter__('startIndex', function() { | |
return this._startIndex; | |
}); | |
this.__defineSetter__('startIndex', function(value) { | |
this._startIndex = Number(value); | |
}); | |
this._streams = []; | |
if(this.config.url) { | |
this._streams.push({ url: this.config.url, bitrate: this.config.bitrate }); | |
} | |
if(this.config.streams instanceof Array) { | |
this.config.streams.forEach(function(stream) { | |
if(stream.url) { | |
this._streams.push({ url: stream.url, bitrate: stream.bitrate || this.config.bitrate }); | |
} else { | |
throw new Error("Invalid stream: must at minimum provide a URL"); | |
} | |
}, this); | |
} | |
this._isSorted = false; | |
// intentionally no setter here, use helpers below | |
this.__defineGetter__('streams', this._streamsGetter); | |
}, | |
streamsReady: function(callback) { | |
// This method is called by the mediaplayer to see if streams are ready before fetching the streams | |
// If you have streams which expire quickly and need to be refetched immediately before playback | |
// override this method, save the callback passed in as the parameter to this function and return false. | |
// Once your streams are ready, then you would call the callback handler and the mediaplayer will resume | |
// like normal. Please note, this method will be called again after you call the callback handler, | |
// so you need to insure you return true the second time or else you could get stuck in an endless | |
// loop of fetching fresh urls. | |
return true; | |
}, | |
_streamsGetter: function() { | |
if(!this._isSorted) { | |
this._streams.sort(function(a, b) { | |
if (Number(a.bitrate) == Number(b.bitrate)) { | |
return 0; | |
} | |
return Number(a.bitrate) < Number(b.bitrate) ? 1 : -1; | |
}); | |
this._isSorted = true; | |
} | |
return this._streams; | |
}, | |
// important to set _isSorted flag to false if adding any additional setters which add to _streams | |
addURL: function(url, bitrate) { | |
if(url) { | |
this._isSorted = false; | |
this._streams.push({ url: url, bitrate: bitrate }); | |
} else { | |
throw new Error("Invalid stream: must at minimum provide a URL"); | |
} | |
return this; | |
} | |
}); | |
} // end patch conditional loader | |
KONtx_automation_log("endjs","mediaplayer.js"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment