modularize media player

This commit is contained in:
Luke 2015-06-07 16:06:18 -04:00
parent db61c30557
commit 440bfc832a
3 changed files with 353 additions and 159 deletions

View File

@ -282,7 +282,7 @@
}
}
@media all and (max-width: 1200px) {
@media all and (max-width: 800px) {
/* They can use the left menu */
.dashboardEntryHeaderButton {

View File

@ -271,7 +271,7 @@
var mediaControls = $("#videoPlayer");
var state = self.getPlayerStateInternal(self.currentMediaElement, item, self.currentMediaSource);
var state = self.getPlayerStateInternal(self.currentMediaRenderer, item, self.currentMediaSource);
var url = "";
var imageWidth = 400;
@ -751,7 +751,7 @@
return currentStream.Type == "Audio";
});
var currentIndex = getParameterByName('AudioStreamIndex', self.getCurrentSrc(self.currentMediaElement));
var currentIndex = getParameterByName('AudioStreamIndex', self.getCurrentSrc(self.currentMediaRenderer));
var html = '';
html += '<div class="videoPlayerPopupContent">';
@ -917,7 +917,7 @@
function getQualityFlyoutHtml() {
var currentSrc = self.getCurrentSrc(self.currentMediaElement).toLowerCase();
var currentSrc = self.getCurrentSrc(self.currentMediaRenderer).toLowerCase();
var isStatic = currentSrc.indexOf('static=true') != -1;
var videoStream = self.currentMediaSource.MediaStreams.filter(function (stream) {
@ -1042,13 +1042,11 @@
};
// Replace audio version
self.cleanup = function (playerElement) {
self.cleanup = function (mediaRenderer) {
if (playerElement.tagName.toLowerCase() == 'video') {
currentTimeElement.html('--:--');
currentTimeElement.html('--:--');
unbindEventsForPlayback();
}
unbindEventsForPlayback();
};
self.playVideo = function (item, mediaSource, startPosition) {
@ -1189,41 +1187,18 @@
videoControls.removeClass('hide');
}
var video = $("video", videoElement);
var mediaRenderer = new HtmlMediaRenderer('video');
initialVolume = self.getSavedVolume();
video.each(function () {
this.volume = initialVolume;
});
mediaRenderer.volume(initialVolume);
volumeSlider.val(initialVolume).slider('refresh');
updateVolumeButtons(initialVolume);
video.one("loadedmetadata.mediaplayerevent", function (e) {
$(mediaRenderer).on("volumechange.mediaplayerevent", function (e) {
// The IE video player won't autoplay without this
if ($.browser.msie) {
this.play();
}
}).one("playing.mediaplayerevent", function (e) {
// TODO: This is not working in chrome. Is it too early?
// Appending #t=xxx to the query string doesn't seem to work with HLS
if (startPositionInSeekParam && this.currentSrc && this.currentSrc.toLowerCase().indexOf('.m3u8') != -1) {
var element = this;
setTimeout(function () {
element.currentTime = startPositionInSeekParam;
}, 3000);
}
}).on("volumechange.mediaplayerevent", function (e) {
var vol = this.volume;
updateVolumeButtons(vol);
updateVolumeButtons(this.volume());
}).one("playing.mediaplayerevent", function () {
@ -1262,9 +1237,6 @@
self.stop();
var errorCode = this.error ? this.error.code : '';
console.log('Html5 Video error code: ' + errorCode);
var errorMsg = Globalize.translate('MessageErrorPlayingVideo');
if (item.Type == "TvChannel") {
@ -1289,7 +1261,7 @@
}).on("click.mediaplayerevent", function (e) {
if (!$.browser.mobile) {
if (this.paused) {
if (this.paused()) {
self.unpause();
} else {
self.pause();
@ -1311,7 +1283,7 @@
$('body').addClass('bodyWithPopupOpen');
self.currentMediaElement = video[0];
self.currentMediaRenderer = mediaRenderer;
self.currentDurationTicks = self.currentMediaSource.RunTimeTicks;
self.updateNowPlayingInfo(item);

View File

@ -4,12 +4,11 @@
var self = this;
var testableVideoElement = document.createElement('video');
var currentProgressInterval;
var canClientSeek;
var currentPlaylistIndex = 0;
self.currentMediaElement = null;
self.currentMediaRenderer = null;
self.currentItem = null;
self.currentMediaSource = null;
@ -395,20 +394,20 @@
return profile;
};
self.updateCanClientSeek = function (elem) {
self.updateCanClientSeek = function (mediaRenderer) {
var duration = elem.duration;
var duration = mediaRenderer.duration();
canClientSeek = duration && !isNaN(duration) && duration != Number.POSITIVE_INFINITY && duration != Number.NEGATIVE_INFINITY;
};
self.getCurrentSrc = function (mediaElement) {
return mediaElement.currentSrc;
self.getCurrentSrc = function (mediaRenderer) {
return mediaRenderer.currentSrc();
};
self.getCurrentTicks = function (mediaElement) {
self.getCurrentTicks = function (mediaRenderer) {
var playerTime = Math.floor(10000000 * (mediaElement || self.currentMediaElement).currentTime);
var playerTime = Math.floor(10000000 * (mediaRenderer || self.currentMediaRenderer).currentTime());
playerTime += self.startTimeTicksOffset;
@ -429,7 +428,7 @@
currentProgressInterval = setInterval(function () {
if (self.currentMediaElement) {
if (self.currentMediaRenderer) {
if ((new Date().getTime() - self.lastProgressReport) > intervalTime) {
@ -449,7 +448,7 @@
self.canPlayNativeHls = function () {
var media = testableVideoElement;
var media = document.createElement('video');
// safari
if (media.canPlayType('application/x-mpegURL').replace(/no/, '') ||
@ -472,17 +471,17 @@
self.changeStream = function (ticks, params) {
var element = self.currentMediaElement;
var mediaRenderer = self.currentMediaRenderer;
if (canClientSeek && params == null) {
element.currentTime = ticks / (1000 * 10000);
mediaRenderer.currentTime(ticks / (1000 * 10000));
return;
}
params = params || {};
var currentSrc = element.currentSrc;
var currentSrc = mediaRenderer.currentSrc();
var playSessionId = getParameterByName('PlaySessionId', currentSrc);
var liveStreamId = getParameterByName('LiveStreamId', currentSrc);
@ -490,7 +489,7 @@
if (params.AudioStreamIndex == null && params.SubtitleStreamIndex == null && params.Bitrate == null) {
currentSrc = replaceQueryString(currentSrc, 'starttimeticks', ticks || 0);
changeStreamToUrl(element, playSessionId, currentSrc, ticks);
changeStreamToUrl(mediaRenderer, playSessionId, currentSrc, ticks);
return;
}
@ -514,23 +513,16 @@
self.currentSubtitleStreamIndex = subtitleStreamIndex;
currentSrc = ApiClient.getUrl(self.currentMediaSource.TranscodingUrl);
changeStreamToUrl(element, playSessionId, currentSrc, ticks);
changeStreamToUrl(mediaRenderer, playSessionId, currentSrc, ticks);
}
});
};
function changeStreamToUrl(element, playSessionId, url, newPositionTicks) {
function changeStreamToUrl(mediaRenderer, playSessionId, url, newPositionTicks) {
clearProgressInterval();
$(element).off('ended.playbackstopped').off('ended.playnext').one("loadedmetadata.mediaplayerevent", function (e) {
// The IE video player won't autoplay without this
if ($.browser.msie && self.currentItem.MediaType == "Video") {
this.play();
}
}).one("play", function () {
$(mediaRenderer).off('ended.playbackstopped').off('ended.playnext').one("play", function () {
self.updateCanClientSeek(this);
@ -545,15 +537,14 @@
ApiClient.stopActiveEncodings(playSessionId).done(function () {
self.startTimeTicksOffset = newPositionTicks;
element.src = url;
mediaRenderer.setCurrentSrc(url);
});
self.updateTextStreamUrls(newPositionTicks || 0);
} else {
self.startTimeTicksOffset = newPositionTicks;
element.src = url;
element.play();
mediaRenderer.setCurrentSrc(url);
}
}
@ -587,7 +578,7 @@
currentTimeElement.html(timeText);
}
var state = self.getPlayerStateInternal(self.currentMediaElement, self.currentItem, self.currentMediaSource);
var state = self.getPlayerStateInternal(self.currentMediaRenderer, self.currentItem, self.currentMediaSource);
$(self).trigger('positionchange', [state]);
};
@ -902,7 +893,7 @@
} else if (item.MediaType === "Audio") {
self.currentMediaElement = playAudio(item, self.currentMediaSource, startPosition);
self.currentMediaRenderer = playAudio(item, self.currentMediaSource, startPosition);
self.currentDurationTicks = self.currentMediaSource.RunTimeTicks;
}
@ -1071,7 +1062,7 @@
self.queue = function (options) {
if (!self.currentMediaElement) {
if (!self.playlist.length) {
self.play(options);
return;
}
@ -1105,7 +1096,7 @@
self.queueNext = function (options) {
if (!self.currentMediaElement) {
if (!self.playlist.length) {
self.play(options);
return;
}
@ -1136,11 +1127,11 @@
self.pause = function () {
self.currentMediaElement.pause();
self.currentMediaRenderer.pause();
};
self.unpause = function () {
self.currentMediaElement.play();
self.currentMediaRenderer.unpause();
};
self.seek = function (position) {
@ -1159,12 +1150,12 @@
};
self.volume = function () {
return self.currentMediaElement.volume * 100;
return self.currentMediaRenderer.volume() * 100;
};
self.toggleMute = function () {
if (self.currentMediaElement) {
if (self.currentMediaRenderer) {
console.log('MediaPlayer toggling mute');
@ -1178,14 +1169,14 @@
self.volumeDown = function () {
if (self.currentMediaElement) {
if (self.currentMediaRenderer) {
self.setVolume(Math.max(self.volume() - 2, 0));
}
};
self.volumeUp = function () {
if (self.currentMediaElement) {
if (self.currentMediaRenderer) {
self.setVolume(Math.min(self.volume() + 2, 100));
}
};
@ -1193,14 +1184,14 @@
// Sets volume using a 0-100 scale
self.setVolume = function (val) {
if (self.currentMediaElement) {
if (self.currentMediaRenderer) {
console.log('MediaPlayer setting volume to ' + val);
self.currentMediaElement.volume = val / 100;
self.currentMediaRenderer.volume(val / 100);
self.onVolumeChanged(self.currentMediaElement);
self.onVolumeChanged(self.currentMediaRenderer);
self.saveVolume();
//self.saveVolume();
}
};
@ -1333,36 +1324,25 @@
self.stop = function () {
var elem = self.currentMediaElement;
var mediaRenderer = self.currentMediaRenderer;
if (elem) {
if (mediaRenderer) {
elem.pause();
mediaRenderer.pause();
$(elem).off("ended.playnext").one("ended", function () {
$(mediaRenderer).off("ended.playnext").one("ended", function () {
$(this).off();
if (this.tagName.toLowerCase() != 'audio') {
$(this).remove();
}
elem.src = null;
elem.src = "";
self.currentMediaElement = null;
this.destroy();
self.currentMediaRenderer = null;
self.currentItem = null;
self.currentMediaSource = null;
// When the browser regains focus it may start auto-playing the last video
if ($.browser.safari) {
elem.src = 'files/dummy.mp4';
elem.play();
}
}).trigger("ended");
} else {
self.currentMediaElement = null;
self.currentMediaRenderer = null;
self.currentItem = null;
self.currentMediaSource = null;
}
@ -1374,34 +1354,34 @@
};
self.isPlaying = function () {
return self.currentMediaElement != null;
return self.playlist.length > 0;
};
self.getPlayerState = function () {
var deferred = $.Deferred();
var result = self.getPlayerStateInternal(self.currentMediaElement, self.currentItem, self.currentMediaSource);
var result = self.getPlayerStateInternal(self.currentMediaRenderer, self.currentItem, self.currentMediaSource);
deferred.resolveWith(null, [result]);
return deferred.promise();
};
self.getPlayerStateInternal = function (playerElement, item, mediaSource) {
self.getPlayerStateInternal = function (mediaRenderer, item, mediaSource) {
var state = {
PlayState: {}
};
if (playerElement) {
if (mediaRenderer) {
state.PlayState.VolumeLevel = playerElement.volume * 100;
state.PlayState.IsMuted = playerElement.volume == 0;
state.PlayState.IsPaused = playerElement.paused;
state.PlayState.PositionTicks = self.getCurrentTicks(playerElement);
state.PlayState.VolumeLevel = mediaRenderer.volume() * 100;
state.PlayState.IsMuted = mediaRenderer.volume() == 0;
state.PlayState.IsPaused = mediaRenderer.paused();
state.PlayState.PositionTicks = self.getCurrentTicks(mediaRenderer);
var currentSrc = playerElement.currentSrc;
var currentSrc = mediaRenderer.currentSrc();
if (currentSrc) {
@ -1521,22 +1501,22 @@
// Nothing to setup here
};
self.onPlaybackStart = function (playerElement, item, mediaSource) {
self.onPlaybackStart = function (mediaRenderer, item, mediaSource) {
self.updateCanClientSeek(playerElement);
self.updateCanClientSeek(mediaRenderer);
var state = self.getPlayerStateInternal(playerElement, item, mediaSource);
var state = self.getPlayerStateInternal(mediaRenderer, item, mediaSource);
$(self).trigger('playbackstart', [state]);
self.startProgressInterval();
};
self.onVolumeChanged = function (playerElement) {
self.onVolumeChanged = function (mediaRenderer) {
self.saveVolume(playerElement.volume);
self.saveVolume(mediaRenderer.volume());
var state = self.getPlayerStateInternal(playerElement, self.currentItem, self.currentMediaSource);
var state = self.getPlayerStateInternal(mediaRenderer, self.currentItem, self.currentMediaSource);
$(self).trigger('volumechange', [state]);
};
@ -1551,11 +1531,11 @@
$('body').removeClass('bodyWithPopupOpen');
var playerElement = this;
var mediaRenderer = this;
$(playerElement).off('.mediaplayerevent').off('ended.playbackstopped');
$(mediaRenderer).off('.mediaplayerevent').off('ended.playbackstopped');
self.cleanup(playerElement);
self.cleanup(mediaRenderer);
clearProgressInterval();
@ -1570,14 +1550,14 @@
self.resetEnhancements();
}
var state = self.getPlayerStateInternal(playerElement, item, mediaSource);
var state = self.getPlayerStateInternal(mediaRenderer, item, mediaSource);
$(self).trigger('playbackstop', [state]);
};
self.onPlaystateChange = function (playerElement) {
self.onPlaystateChange = function (mediaRenderer) {
var state = self.getPlayerStateInternal(playerElement, self.currentItem, self.currentMediaSource);
var state = self.getPlayerStateInternal(mediaRenderer, self.currentItem, self.currentMediaSource);
$(self).trigger('playstatechange', [state]);
};
@ -1585,16 +1565,15 @@
$(window).on("beforeunload", function () {
// Try to report playback stopped before the browser closes
if (self.currentItem && self.currentMediaElement && currentProgressInterval) {
if (self.currentItem && self.currentMediaRenderer && currentProgressInterval) {
self.onPlaybackStopped.call(self.currentMediaElement);
self.onPlaybackStopped.call(self.currentMediaRenderer);
}
});
function sendProgressUpdate() {
var element = self.currentMediaElement;
var state = self.getPlayerStateInternal(element, self.currentItem, self.currentMediaSource);
var state = self.getPlayerStateInternal(self.currentMediaRenderer, self.currentItem, self.currentMediaSource);
var info = {
QueueableMediaTypes: state.NowPlayingItem.MediaType,
@ -1615,9 +1594,13 @@
}
}
self._canPlayWebm = null;
self.canPlayWebm = function () {
return testableVideoElement.canPlayType('video/webm').replace(/no/, '');
if (self._canPlayWebm == null) {
self._canPlayWebm = document.createElement('video').canPlayType('video/webm').replace(/no/, '');
}
return self._canPlayWebm;
};
self.canAutoPlayAudio = function () {
@ -1633,30 +1616,9 @@
return true;
};
function getAudioElement() {
function getAudioRenderer() {
var elem = $('.mediaPlayerAudio');
if (elem.length) {
return elem;
}
var html = '';
var requiresControls = !self.canAutoPlayAudio();
if (requiresControls) {
html += '<div class="mediaPlayerAudioContainer"><div class="mediaPlayerAudioContainerInner">';;
} else {
html += '<div class="mediaPlayerAudioContainer" style="display:none;"><div class="mediaPlayerAudioContainerInner">';;
}
html += '<audio class="mediaPlayerAudio" crossorigin="anonymous" controls>';
html += '</audio></div></div>';
$(document.body).append(html);
return $('.mediaPlayerAudio');
return new HtmlMediaRenderer('audio');
}
function onTimeUpdate() {
@ -1673,14 +1635,13 @@
var initialVolume = self.getSavedVolume();
return getAudioElement().each(function () {
var mediaRenderer = getAudioRenderer();
this.src = audioUrl;
this.volume = initialVolume;
this.poster = self.getPosterUrl(item);
this.play();
mediaRenderer.setCurrentSrc(audioUrl);
mediaRenderer.volume(initialVolume);
mediaRenderer.setPoster(self.getPosterUrl(item));
}).on("volumechange.mediaplayerevent", function () {
$(mediaRenderer).on("volumechange.mediaplayerevent", function () {
console.log('audio element event: volumechange');
@ -1690,8 +1651,6 @@
console.log('audio element event: playing');
$('.mediaPlayerAudioContainer').hide();
// For some reason this is firing at the start, so don't bind until playback has begun
$(this).on("ended.playbackstopped", self.onPlaybackStopped).one('ended.playnext', self.playNextAfterEnded);
@ -1715,7 +1674,9 @@
// In the event timeupdate isn't firing, at least we can update when this happens
self.setCurrentTime(self.getCurrentTicks());
}).on("timeupdate.mediaplayerevent", onTimeUpdate)[0];
}).on("timeupdate.mediaplayerevent", onTimeUpdate);
return mediaRenderer;
};
var getItemFields = "MediaSources,Chapters";
@ -1736,4 +1697,265 @@
});
})(document, setTimeout, clearTimeout, screen, window.appStorage, $, setInterval, window);
})(document, setTimeout, clearTimeout, screen, window.appStorage, $, setInterval, window);
(function(){
function htmlMediaRenderer(type) {
var mediaElement;
var self = this;
function onEnded() {
$(self).trigger('ended');
}
function onTimeUpdate() {
$(self).trigger('timeupdate');
}
function onVolumeChange() {
$(self).trigger('volumechange');
}
function onOneAudioPlaying() {
$('.mediaPlayerAudioContainer').hide();
}
function onPlaying() {
$(self).trigger('playing');
}
function onPlay() {
$(self).trigger('play');
}
function onPause() {
$(self).trigger('pause');
}
function onClick() {
$(self).trigger('click');
}
function onDblClick() {
$(self).trigger('dblclick');
}
function onError() {
var errorCode = this.error ? this.error.code : '';
console.log('Media element error code: ' + errorCode);
$(self).trigger('error');
}
function onLoadedMetadata() {
// The IE video player won't autoplay without this
if ($.browser.msie) {
this.play();
}
}
function onOneVideoPlaying() {
var currentSrc = (this.currentSrc || '').toLowerCase();
var parts = currentSrc.split('#');
if (parts.length > 1) {
var parts = parts[parts.length -1].split('=');
if (parts.length == 2) {
var startPositionInSeekParam = parseFloat(parts[1]);
// Appending #t=xxx to the query string doesn't seem to work with HLS
if (startPositionInSeekParam && currentSrc.indexOf('.m3u8') != -1) {
var element = this;
setTimeout(function () {
element.currentTime = startPositionInSeekParam;
}, 3000);
}
}
}
}
function createAudioElement() {
var elem = $('.mediaPlayerAudio');
if (!elem.length) {
var html = '';
var requiresControls = !MediaPlayer.canAutoPlayAudio();
if (requiresControls) {
html += '<div class="mediaPlayerAudioContainer"><div class="mediaPlayerAudioContainerInner">';;
} else {
html += '<div class="mediaPlayerAudioContainer" style="display:none;"><div class="mediaPlayerAudioContainerInner">';;
}
html += '<audio class="mediaPlayerAudio" crossorigin="anonymous" controls>';
html += '</audio></div></div>';
$(document.body).append(html);
elem = $('.mediaPlayerAudio');
}
return $(elem)
.on('timeupdate', onTimeUpdate)
.on('ended', onEnded)
.on('volumechange', onVolumeChange)
.one('playing', onOneAudioPlaying)
.on('play', onPlay)
.on('pause', onPause)
.on('playing', onPlaying)
.on('error', onError)[0];
}
function createVideoElement() {
var elem = $('.itemVideo');
return $(elem)
.one('.loadedmetadata')
.one('playing', onOneVideoPlaying)
.on('timeupdate', onTimeUpdate)
.on('ended', onEnded)
.on('volumechange', onVolumeChange)
.on('play', onPlay)
.on('pause', onPause)
.on('playing', onPlaying)
.on('error', onError)[0];
}
self.currentTime = function(val) {
if (mediaElement) {
if (val != null) {
mediaElement.currentTime = val;
return;
}
return mediaElement.currentTime;
}
};
self.duration = function(val) {
if (mediaElement) {
return mediaElement.duration;
}
return null;
};
self.pause = function() {
if (mediaElement) {
mediaElement.pause();
}
};
self.unpause = function() {
if (mediaElement) {
mediaElement.play();
}
};
self.volume = function(val) {
if (mediaElement) {
if (val != null) {
mediaElement.volume = val;
return;
}
return mediaElement.volume;
}
};
self.setCurrentSrc = function(val) {
var elem = mediaElement;
if (!elem) {
return;
}
if (!val) {
elem.src = null;
elem.src = "";
// When the browser regains focus it may start auto-playing the last video
if ($.browser.safari) {
elem.src = 'files/dummy.mp4';
elem.play();
}
return;
}
elem.src = val;
if (elem.tagName.toLowerCase() == 'audio') {
elem.play();
}
else {
$(elem).one("loadedmetadata.mediaplayerevent", onLoadedMetadata);
}
};
self.currentSrc = function() {
if (mediaElement) {
return mediaElement.currentSrc;
}
};
self.paused = function() {
if (mediaElement) {
return mediaElement.paused;
}
return false;
};
self.destroy = function() {
self.setCurrentSrc(null);
var elem = mediaElement;
if (elem) {
$(elem).off();
if (elem.tagName.toLowerCase() != 'audio') {
$(elem).remove();
}
}
};
self.setPoster = function(url) {
var elem = mediaElement;
if (elem) {
elem.poster = url;
}
};
if (type == 'audio') {
mediaElement = createAudioElement();
}
else {
mediaElement = createVideoElement();
}
}
window.HtmlMediaRenderer = htmlMediaRenderer;
})();