diff --git a/dashboard-ui/css/images/media/pause.png b/dashboard-ui/css/images/media/pause.png new file mode 100644 index 0000000000..eb4c7831d5 Binary files /dev/null and b/dashboard-ui/css/images/media/pause.png differ diff --git a/dashboard-ui/css/images/media/play.png b/dashboard-ui/css/images/media/play.png new file mode 100644 index 0000000000..d6fa91c641 Binary files /dev/null and b/dashboard-ui/css/images/media/play.png differ diff --git a/dashboard-ui/css/mediaplayer.css b/dashboard-ui/css/mediaplayer.css index 35dd7b44a1..f3348f0371 100644 --- a/dashboard-ui/css/mediaplayer.css +++ b/dashboard-ui/css/mediaplayer.css @@ -1,27 +1,33 @@ /* Now playing bar */ .nowPlayingBar { - padding: 6px 0 24px 0; - border-top: 2px solid green; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; } - .nowPlayingBar .highPosition { - z-index: 99999; - position: relative; - opacity: 0.5; - } - - .nowPlayingBar .highPosition:hover:not(.barBackground ) { - opacity: 0.9; - } - .nowPlayingBar .barBackground { border-top: 2px solid green; position: absolute; - margin: -8px -0.5em -26px !important; width: 100%; height: 100%; } + #videoPlayer.fullscreenVideo .nowPlayingBar { + opacity: 0.5; + -webkit-transition: top 0.6s ease-in-out; + -moz-transition: top 0.6s ease-in-out; + -o-transition: top 0.6s ease-in-out; + transition: top 0.6s ease-in-out; + top: 100%; + height: 100%; + } + + #videoPlayer.fullscreenVideo:hover .nowPlayingBar { + top: 95% !important; + } + .nowPlayingBar > *:not(#mediaElement):not(.mediaFlyoutContainer):not(.barBackground ) { position: relative; } @@ -39,6 +45,15 @@ position: relative; } +.fullscreenVideo #mediaElement { + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + .nowPlayingMediaInfo div { display: inline-block; } @@ -49,15 +64,17 @@ .nowPlayingMediaInfo { + position: relative; display: inline-block; + margin-top: 3px; + vertical-align: top; } .nowPlayingText { position: relative; - top: -3px; - margin-left: 3px; - margin-right: 2em; + margin: 0 2em 0 5px; font-weight: normal; + top: -7px; } @media all and (max-width: 650px) { @@ -75,7 +92,6 @@ background: #000; border: 1px solid #444; width: 320px; - cursor: pointer; margin: 20px; position: fixed; top: 50%; @@ -112,7 +128,6 @@ z-index: 99997; background: #1d1d1d; position: fixed; - overflow: hidden; border: 1px solid green; top: 50%; left: 50%; @@ -130,15 +145,12 @@ width: 100% !important; height: 100% !important; border: 0 !important; - z-index: 999999 !important; margin: 0 !important; } - #videoPlayer #mediaElement { - position: absolute; - top: 0; - left: 0; - } +.fullscreenVideo .itemVideo { + z-index: 99996; +} #videoPlayer .itemVideo { position: static; @@ -147,19 +159,23 @@ } #videoPlayer .nowPlayingBar { + z-index: 99999; padding: 0; - border: none !important; - height: 40px; - overflow: hidden; + height: 50px; background-color: #1d1d1d; position: absolute; + top: initial; right: 0; - bottom: 10px; + bottom: 0; left: 0; } #videoPlayer .nowPlayingBar .barBackground { - display: none; + border-width: 0; + } + + #videoPlayer.fullscreenVideo .nowPlayingBar .barBackground { + border-width: 2px; } .currentTime { @@ -181,6 +197,7 @@ .sliderContainer { display: inline-block; + margin-top: 10px; } .positionSliderContainer { @@ -240,21 +257,24 @@ input[type="range"]::-ms-fill-upper { } } +@media all and (max-width: 640px) { + .positionSliderContainer { + width: 80px; + } +} + @media all and (max-width: 600px) { .chaptersButton, .audioTracksButton, .sendMediaButton { display: none!important; } - .positionSliderContainer, .currentTime { + .positionSliderContainer { top: 0!important; position: relative!important; } } @media all and (max-width: 500px) { - .positionSliderContainer { - width: 80px; - } .previousTrackButton, .nextTrackButton { display: none!important; @@ -278,13 +298,31 @@ input[type="range"]::-ms-fill-upper { border: 1px solid #999; position: absolute; z-index: 99999; - bottom: 78px; + bottom: 64px; margin-left: -50px; max-height: 300px; overflow-y: auto; font-size: 13px; } +.fullscreenVideo .mediaPlayerFlyout { + -webkit-transition: top 0.6s ease-in-out; + -moz-transition: top 0.6s ease-in-out; + -o-transition: top 0.6s ease-in-out; + transition: top 0.6s ease-in-out; + top: 100%; + bottom: 100%; +} + +#videoPlayer.fullscreenVideo:hover .mediaPlayerFlyout { + -webkit-transition: top 0.6s ease-out; + -moz-transition: top 0.6s ease-out; + -o-transition: top 0.6s ease-out; + transition: top 0.6s ease-out; + top: -303px !important; + bottom: -3px !important; +ss} + .chaptersFlyout { width: 250px; } @@ -420,4 +458,81 @@ input[type="range"]::-ms-fill-upper { .positionSliderContainer { width: 300px; } +} + +.status { + position: absolute; + top: 50%; + left: 50%; + margin-top: -75px; + margin-left: -75px; + width: 150px; + height: 150px; + z-index: 99999; +} + +#play { + background-image: url(images/media/play.png); + opacity: 0; + display: none; +} + +#pause { + background-image: url(images/media/pause.png); + opacity: 0; + display: none; +} + +.fadeOut { + animation-name: fadeOut; + -webkit-animation-name: fadeOut; + animation-duration: .25s; + -webkit-animation-duration: .25s; + animation-timing-function: ease-in-out; + -webkit-animation-timing-function: ease-in-out; + visibility: visible !important; +} + +@keyframes fadeOut { + 0% { + transform: scale(.25); + opacity: 0.7; + } + + 60% { + transform: scale(.5); + opacity: 0.5; + } + + 80% { + transform: scale(.75); + opacity: 0.3; + } + + 100% { + transform: scale(1); + opacity: 0; + } +} + +@-webkit-keyframes fadeOut { + 0% { + -webkit-transform: scale(.25); + opacity: 0.7; + } + + 60% { + -webkit-transform: scale(.5); + opacity: 0.5; + } + + 80% { + -webkit-transform: scale(.75); + opacity: 0.3; + } + + 100% { + -webkit-transform: scale(1); + opacity: 0; + } } \ No newline at end of file diff --git a/dashboard-ui/css/site.css b/dashboard-ui/css/site.css index 6d4e081c19..199da24dcc 100644 --- a/dashboard-ui/css/site.css +++ b/dashboard-ui/css/site.css @@ -683,6 +683,7 @@ h1 .imageLink { z-index: 99997; color: #fff; border: 0 !important; + height: 50px; } .footerNotification { diff --git a/dashboard-ui/scripts/mediaplayer-video.js b/dashboard-ui/scripts/mediaplayer-video.js new file mode 100644 index 0000000000..55f833939b --- /dev/null +++ b/dashboard-ui/scripts/mediaplayer-video.js @@ -0,0 +1,1022 @@ +(function () { + videoPlayer = function(mediaPlayer, item, startPosition, user) { + if (mediaPlayer == null) { + throw new Error("mediaPlayer cannot be null"); + } + + if (item == null) { + throw new Error("item cannot be null"); + } + + if (user == null) { + throw new Error("user cannot be null"); + } + + var self = mediaPlayer; + + var currentItem; + var timeout; + var video; + var culturesPromise; + var idleState = true; + var fullscreenExited = false; + + self.initVideoPlayer = function () { + video = playVideo(item, startPosition, user); + enhancePlayer(); + return video + }; + + self.toggleFullscreen = function () { + if (self.isFullScreen()) { + if (document.cancelFullScreen) { document.cancelFullScreen(); } + else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } + else if (document.webkitCancelFullScreen) { + document.webkitCancelFullScreen(); + } + $('#videoPlayer').removeClass('fullscreenVideo'); + } else { + requestFullScreen(document.body); + } + }; + + self.resetEnhancements = function () { + var footer = $("#footer"); + var videoBackdrop = $("#videoBackdrop", footer); + var nowPlayingBar = $("#nowPlayingBar", videoBackdrop); + footer.append(nowPlayingBar); + videoBackdrop.remove(); + }; + + self.exitFullScreen = function () { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.mozExitFullScreen) { + document.mozExitFullScreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } + + fullscreenExited = true; + } + + self.isFullScreen = function () { + return document.fullscreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement ? true : false; + } + + self.showSubtitleMenu = function () { + + var flyout = $('#subtitleFlyout'); + + if (!flyout.is(':visible')) { + + culturesPromise = culturesPromise || ApiClient.getCultures(); + + $("html").css("cursor", "progress"); + + culturesPromise.done(function (cultures) { + $("html").css("cursor", "default"); + flyout.html(getSubtitleTracksHtml(currentItem, cultures)).trigger('create').scrollTop(0); + toggleFlyout(flyout, '#subtitleButton'); + }); + + } else { + toggleFlyout(flyout, '#subtitleButton'); + } + }; + + self.showQualityFlyout = function () { + + var flyout = $('#qualityFlyout'); + + if (!flyout.is(':visible')) { + flyout.html(getQualityFlyoutHtml(currentItem)).scrollTop(0); + } + + toggleFlyout(flyout, '#qualityButton'); + }; + + self.showChaptersFlyout = function () { + + var flyout = $('#chaptersFlyout'); + + if (!flyout.is(':visible')) { + flyout.html(getChaptersFlyoutHtml(currentItem)).scrollTop(0); + } + + toggleFlyout(flyout, '#chaptersButton'); + }; + + self.showAudioTracksFlyout = function () { + + var flyout = $('#audioTracksFlyout'); + + if (!flyout.is(':visible')) { + + culturesPromise = culturesPromise || ApiClient.getCultures(); + + $("html").css("cursor", "progress"); + + culturesPromise.done(function (cultures) { + $("html").css("cursor", "default"); + flyout.html(getAudioTracksHtml(currentItem, cultures)).trigger('create').scrollTop(0); + toggleFlyout(flyout, '#audioTracksButton'); + }); + } else { + toggleFlyout(flyout, '#audioTracksButton'); + } + }; + + $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange', function () { + var nowPlayingBar = $('#nowPlayingBar'); + if (self.isFullScreen()) { + enterFullScreen(); + idleState = true; + } else { + nowPlayingBar.removeClass("highPosition"); + exitFullScreenToWindow(); + } + }); + + $(function () { + $('#chaptersFlyout').on('click', '.mediaFlyoutOption', function () { + + var ticks = parseInt(this.getAttribute('data-positionticks')); + + self.changeStream(ticks); + + hideFlyout($('#chaptersFlyout')); + }); + + $('#audioTracksFlyout').on('click', '.mediaFlyoutOption', function () { + + if (!$(this).hasClass('selectedMediaFlyoutOption')) { + var index = parseInt(this.getAttribute('data-index')); + + self.changeStream(self.getCurrentTicks(), { AudioStreamIndex: index }); + } + + hideFlyout($('#audioTracksFlyout')); + }); + + $('#subtitleFlyout').on('click', '.mediaFlyoutOption', function () { + + if (!$(this).hasClass('selectedMediaFlyoutOption')) { + var index = parseInt(this.getAttribute('data-index')); + + self.changeStream(self.getCurrentTicks(), { SubtitleStreamIndex: index }); + } + + hideFlyout($('#subtitleFlyout')); + }); + + $('#qualityFlyout').on('click', '.mediaFlyoutOption', function () { + + if (!$(this).hasClass('selectedMediaFlyoutOption')) { + + var maxWidth = parseInt(this.getAttribute('data-maxwidth')); + var bitrate = parseInt(this.getAttribute('data-bitrate')); + + localStorage.setItem('preferredVideoBitrate', bitrate); + + self.changeStream(self.getCurrentTicks(), { + + MaxWidth: maxWidth, + Bitrate: bitrate + }); + } + + hideFlyout($('#qualityFlyout')); + }); + }); + + function requestFullScreen(element) { + // Supports most browsers and their versions. + var requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullScreen; + + if (requestMethod) { // Native full screen. + requestMethod.call(element); + } else { + enterFullScreen(); + } + } + + function enhancePlayer() { + // Show loading animation + $(".ui-loader").show(); + $("html").css("cursor", "wait"); + + var footer = $("#footer"); + var nowPlayingBar = $("#nowPlayingBar", footer); + var mediaElement = $("#mediaElement", footer); + + var play = $("
"); + var pause = $("
"); + mediaElement.append(play).append(pause); + + var videoBackdrop = $("
"); + var videoPlayer = $("
") + .append(mediaElement) + .append(nowPlayingBar); + + videoPlayer.hide(); + videoBackdrop.append(videoPlayer); + footer.append(videoBackdrop); + + // Stop playback on browser back button nav + $(window).on("popstate", function () { + + self.stop(); + + return; + + }); + + $(video) + .on("click", function (e) { + + if (this.paused) { + + self.unpause(); + + } else { + + self.pause(); + + } + + }) + .on("dblclick", function () { + + self.toggleFullscreen(); + + }) + .on("seeking", function (e) { + + $("html").css("cursor", "wait"); + + }) + .on("seeked", function (e) { + + $("html").css("cursor", "default"); + + }) + .on("loadstart", function () { + + $("html").css("cursor", "progress"); + + }) + .on("playing", function (e) { + + $(".ui-loader").hide(); + + $("html").css("cursor", "default"); + + videoPlayer.fadeIn(); + + }); + + $(".mediaFlyoutContainer").on("click", "a", function (e) { + + if (confirm("This option will close the video player. Proceed?")) { + + self.stop(); + + } else { + + e.preventDefault(); + + } + + }); + + changeHandler("fullscreenchange"); + changeHandler("mozfullscreenchange"); + changeHandler("webkitfullscreenchange"); + changeHandler("msfullscreenchange"); + + $(document).on("keyup.enhancePlayer", function (e) { + if (fullscreenExited) { + + videoPlayer.removeClass("fullscreenVideo"); + + fullscreenExited = false; + + return; + } + + if (e.keyCode == 27) { + + self.stop(); + + $(this).unbind("keyup.enhancePlayer"); + } + }); + + video.play(); + + fullscreenExited = false; + }; + + function changeHandler(event) { + + document.addEventListener(event, function () { + fullscreenExited = self.isFullScreen() == false; + }); + + } + + function enterFullScreen() { + + var player = $("#videoPlayer") + + player.addClass("fullscreenVideo"); + + }; + + function exitFullScreenToWindow() { + + var player = $("#videoPlayer") + + player.removeClass("fullscreenVideo"); + + } + + function toggleFlyout(flyout, button) { + + $(document.body).off("mousedown.mediaflyout").on("mousedown.mediaflyout", function (e) { + + var elem = $(e.target); + + var flyoutId = flyout[0].id; + var safeItems = button + ',#' + flyoutId; + + if (!elem.is(safeItems) && !elem.parents(safeItems).length) { + + hideFlyout(flyout); + } + + }); + + var visible = $(flyout).is(":visible"); + + if (!visible) { + + flyout.slideDown(); + + } else { + + $(button).blur(); + + hideFlyout(flyout); + } + } + + function hideFlyout(flyout) { + + flyout.slideUp().empty(); + + $(document.body).off("mousedown.hidesearchhints"); + } + + function getChaptersFlyoutHtml(item) { + + var html = ''; + + var currentTicks = self.getCurrentTicks(); + + var chapters = item.Chapters || []; + + for (var i = 0, length = chapters.length; i < length; i++) { + + var chapter = chapters[i]; + + var isSelected = false; + + if (currentTicks >= chapter.StartPositionTicks) { + + var nextChapter = chapters[i + 1]; + + isSelected = !nextChapter || currentTicks < nextChapter.StartPositionTicks; + } + + if (isSelected) { + html += '
'; + } else { + html += '
'; + } + + var imgUrl; + + if (chapter.ImageTag) { + + imgUrl = ApiClient.getImageUrl(item.Id, { + maxwidth: 200, + tag: chapter.ImageTag, + type: "Chapter", + index: i + }); + + } else { + imgUrl = "css/images/media/chapterflyout.png"; + } + + html += ''; + + html += '
'; + + var name = chapter.Name || "Chapter " + (i + 1); + + html += '
' + name + '
'; + html += '
' + Dashboard.getDisplayTime(chapter.StartPositionTicks) + '
'; + + html += '
'; + + html += "
"; + } + + return html; + } + + function getAudioTracksHtml(item, cultures) { + + var streams = item.MediaStreams.filter(function (i) { + return i.Type == "Audio"; + }); + + var currentIndex = getParameterByName('AudioStreamIndex', video.currentSrc); + + var html = ''; + + for (var i = 0, length = streams.length; i < length; i++) { + + var stream = streams[i]; + + if (stream.Index == currentIndex) { + html += '
'; + } else { + html += '
'; + } + + html += ''; + + html += '
'; + + var language = null; + + if (stream.Language && stream.Language != "und") { + + var culture = cultures.filter(function (current) { + return current.ThreeLetterISOLanguageName.toLowerCase() == stream.Language.toLowerCase(); + }); + + if (culture.length) { + language = culture[0].DisplayName; + } + } + + html += '
' + (language || stream.Language || 'Unknown language') + '
'; + + var options = []; + + if (stream.Codec) { + options.push(stream.Codec); + } + if (stream.Profile) { + options.push(stream.Profile); + } + + if (stream.BitRate) { + options.push((Math.floor(stream.BitRate / 1000)) + ' kbps'); + } + + if (stream.Channels) { + options.push(stream.Channels + ' ch'); + } + + if (options.length) { + html += '
' + options.join(' • ') + '
'; + } + + options = []; + + if (stream.IsDefault) { + options.push('Default'); + } + if (stream.IsForced) { + options.push('Forced'); + } + + if (options.length) { + html += '
' + options.join(' • ') + '
'; + } + + html += "
"; + + html += "
"; + } + + html += '
Preferences
'; + + return html; + } + + function getSubtitleTracksHtml(item, cultures) { + + var streams = item.MediaStreams.filter(function (i) { + return i.Type == "Subtitle"; + }); + + var currentIndex = getParameterByName('SubtitleStreamIndex', video.currentSrc) || -1; + + var html = ''; + + streams.unshift({ + Index: -1, + Language: "Off" + }); + + for (var i = 0, length = streams.length; i < length; i++) { + + var stream = streams[i]; + + if (stream.Index == currentIndex) { + html += '
'; + } else { + html += '
'; + } + + if (stream.Index != -1) { + html += ''; + } else { + html += '
'; + } + + html += '
'; + + var language = null; + var options = []; + + if (stream.Language == "Off") { + language = "Off"; + options.push(' '); + } + else if (stream.Language && stream.Language != "und") { + + var culture = cultures.filter(function (current) { + return current.ThreeLetterISOLanguageName.toLowerCase() == stream.Language.toLowerCase(); + }); + + if (culture.length) { + language = culture[0].DisplayName; + } + } + + html += '
' + (language || 'Unknown language') + '
'; + + if (stream.Codec) { + options.push(stream.Codec); + } + + if (options.length) { + html += '
' + options.join(' • ') + '
'; + } + + options = []; + + if (stream.IsDefault) { + options.push('Default'); + } + if (stream.IsForced) { + options.push('Forced'); + } + if (stream.IsExternal) { + options.push('External'); + } + + if (options.length) { + html += '
' + options.join(' • ') + '
'; + } + + html += "
"; + + html += "
"; + } + + html += '
Preferences
'; + + return html; + } + + function getQualityFlyoutHtml(item) { + + var html = ''; + + var currentSrc = video.currentSrc.toLowerCase(); + var isStatic = currentSrc.indexOf('static=true') != -1; + + var transcodingExtension = self.getTranscodingExtension(); + + var currentAudioStreamIndex = getParameterByName('AudioStreamIndex', video.currentSrc); + + var options = getVideoQualityOptions(item, currentAudioStreamIndex, transcodingExtension); + + if (isStatic) { + options[0].name = "Direct"; + } + + for (var i = 0, length = options.length; i < length; i++) { + + var option = options[i]; + + var cssClass = "mediaFlyoutOption"; + + if (option.selected) { + cssClass += " selectedMediaFlyoutOption"; + } + + html += '
'; + + html += '
'; + + html += '
' + option.name + '
'; + + html += "
"; + + html += "
"; + } + + return html; + } + + function getInitialSubtitleStreamIndex(mediaStreams, user) { + + var i, length, mediaStream; + + // Find the first forced subtitle stream + for (i = 0, length = mediaStreams.length; i < length; i++) { + mediaStream = mediaStreams[i]; + + if (mediaStream.Type == "Subtitle" && mediaStream.IsForced) { + return mediaStream.Index; + } + + } + + // If none then look at user configuration + if (user.Configuration.SubtitleLanguagePreference) { + + for (i = 0, length = mediaStreams.length; i < length; i++) { + mediaStream = mediaStreams[i]; + + if (mediaStream.Type == "Subtitle" && mediaStream.Language == user.Configuration.SubtitleLanguagePreference) { + + if (user.Configuration.UseForcedSubtitlesOnly) { + + if (mediaStream.IsForced) { + return mediaStream.Index; + } + + } else { + return mediaStream.Index; + } + } + + } + } + + return null; + } + + function getInitialAudioStreamIndex(mediaStreams, user) { + + // Find all audio streams with at least one channel + var audioStreams = mediaStreams.filter(function (stream) { + return stream.Type == "Audio" && stream.Channels; + }); + + if (user.Configuration.AudioLanguagePreference) { + + for (var i = 0, length = audioStreams.length; i < length; i++) { + var mediaStream = audioStreams[i]; + + if (mediaStream.Language == user.Configuration.AudioLanguagePreference) { + return mediaStream.Index; + } + + } + } + + // Just use the first audio stream + return audioStreams.length ? audioStreams[0].Index : null; + } + + function getVideoQualityOptions(item) { + + var videoStream = (item.MediaStreams || []).filter(function (stream) { + return stream.Type == "Video"; + })[0]; + + var bitrateSetting = parseInt(localStorage.getItem('preferredVideoBitrate') || '') || 1500000; + + var maxAllowedWidth = Math.max(screen.height, screen.width); + + var options = []; + + // We have media info + if (videoStream && videoStream.Width) { + + maxAllowedWidth = videoStream.Width; + } + + // Some 1080- videos are reported as 1912? + if (maxAllowedWidth >= 1910) { + options.push({ name: '1080p - 20Mbps', maxWidth: 1920, bitrate: 20000000 }); + options.push({ name: '1080p - 15Mbps', maxWidth: 1920, bitrate: 15000000 }); + options.push({ name: '1080p - 10Mbps', maxWidth: 1920, bitrate: 10000000 }); + options.push({ name: '1080p - 8Mbps', maxWidth: 1920, bitrate: 8000000 }); + options.push({ name: '1080p - 6Mbps', maxWidth: 1920, bitrate: 6000000 }); + options.push({ name: '1080p - 5Mbps', maxWidth: 1920, bitrate: 5000000 }); + } + else if (maxAllowedWidth >= 1270) { + options.push({ name: '720p - 10Mbps', maxWidth: 1280, bitrate: 10000000 }); + options.push({ name: '720p - 8Mbps', maxWidth: 1280, bitrate: 8000000 }); + options.push({ name: '720p - 6Mbps', maxWidth: 1280, bitrate: 6000000 }); + options.push({ name: '720p - 5Mbps', maxWidth: 1280, bitrate: 5000000 }); + } + else if (maxAllowedWidth >= 470) { + options.push({ name: '480p - 4Mbps', maxWidth: 720, bitrate: 4000000 }); + options.push({ name: '480p - 3.5Mbps', maxWidth: 720, bitrate: 3500000 }); + options.push({ name: '480p - 3Mbps', maxWidth: 720, bitrate: 3000000 }); + options.push({ name: '480p - 2.5Mbps', maxWidth: 720, bitrate: 2500000 }); + options.push({ name: '480p - 2Mbps', maxWidth: 720, bitrate: 2000000 }); + options.push({ name: '480p - 1.5Mbps', maxWidth: 720, bitrate: 1500000 }); + } + + if (maxAllowedWidth >= 1270) { + options.push({ name: '720p - 4Mbps', maxWidth: 1280, bitrate: 4000000 }); + options.push({ name: '720p - 3Mbps', maxWidth: 1280, bitrate: 3000000 }); + options.push({ name: '720p - 2Mbps', maxWidth: 1280, bitrate: 2000000 }); + options.push({ name: '720p - 1.5Mbps', maxWidth: 1280, bitrate: 1500000 }); + } + + options.push({ name: '480p - 1.0Mbps', maxWidth: 720, bitrate: 1000000 }); + options.push({ name: '480p - 720 kbps', maxWidth: 720, bitrate: 700000 }); + options.push({ name: '480p - 420 kbps', maxWidth: 720, bitrate: 420000 }); + options.push({ name: '360p', maxWidth: 640, bitrate: 400000 }); + options.push({ name: '240p', maxWidth: 426, bitrate: 320000 }); + + var i, length, option; + var selectedIndex = -1; + for (i = 0, length = options.length; i < length; i++) { + + option = options[i]; + + if (selectedIndex == -1 && option.bitrate <= bitrateSetting) { + selectedIndex = i; + } + } + + if (selectedIndex == -1) { + + selectedIndex = options.length - 1; + } + + options[selectedIndex].selected = true; + + return options; + } + + function playVideo(item, startPosition, user) { + + var mediaStreams = item.MediaStreams || []; + + var baseParams = { + audioChannels: 2, + StartTimeTicks: startPosition || 0, + SubtitleStreamIndex: getInitialSubtitleStreamIndex(mediaStreams, user), + AudioStreamIndex: getInitialAudioStreamIndex(mediaStreams, user), + deviceId: ApiClient.deviceId(), + Static: false + }; + + var mp4Quality = getVideoQualityOptions(item).filter(function (opt) { + return opt.selected; + })[0]; + mp4Quality = $.extend(mp4Quality, self.getFinalVideoParams(item, mp4Quality.maxWidth, mp4Quality.bitrate, baseParams.AudioStreamIndex, baseParams.SubtitleStreamIndex, '.mp4')); + + var webmQuality = getVideoQualityOptions(item).filter(function (opt) { + return opt.selected; + })[0]; + webmQuality = $.extend(webmQuality, self.getFinalVideoParams(item, webmQuality.maxWidth, webmQuality.bitrate, baseParams.AudioStreamIndex, baseParams.SubtitleStreamIndex, '.webm')); + + var m3U8Quality = getVideoQualityOptions(item).filter(function (opt) { + return opt.selected; + })[0]; + m3U8Quality = $.extend(m3U8Quality, self.getFinalVideoParams(item, mp4Quality.maxWidth, mp4Quality.bitrate, baseParams.AudioStreamIndex, baseParams.SubtitleStreamIndex, '.mp4')); + + // Webm must be ahead of mp4 due to the issue of mp4 playing too fast in chrome + var prioritizeWebmOverH264 = $.browser.chrome || $.browser.msie; + + var isStatic = mp4Quality.isStatic; + + self.startTimeTicksOffset = isStatic ? 0 : startPosition || 0; + + var seekParam = isStatic && startPosition ? '#t=' + (startPosition / 10000000) : ''; + + var mp4VideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.mp4', $.extend({}, baseParams, { + profile: 'baseline', + level: 3, + Static: isStatic, + maxWidth: mp4Quality.maxWidth, + videoBitrate: mp4Quality.videoBitrate, + audioBitrate: mp4Quality.audioBitrate, + VideoCodec: mp4Quality.videoCodec, + AudioCodec: mp4Quality.audioCodec + + })) + seekParam; + + var webmVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.webm', $.extend({}, baseParams, { + + VideoCodec: 'vpx', + AudioCodec: 'Vorbis', + maxWidth: webmQuality.maxWidth, + videoBitrate: webmQuality.videoBitrate, + audioBitrate: webmQuality.audioBitrate + + })) + seekParam; + + var hlsVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.m3u8', $.extend({}, baseParams, { + profile: 'baseline', + level: 3, + timeStampOffsetMs: 0, + maxWidth: m3U8Quality.maxWidth, + videoBitrate: m3U8Quality.videoBitrate, + audioBitrate: m3U8Quality.audioBitrate, + VideoCodec: m3U8Quality.videoCodec, + AudioCodec: m3U8Quality.audioCodec + + })) + seekParam; + + var html = ''; + + var requiresControls = $.browser.msie || $.browser.android || ($.browser.webkit && !$.browser.chrome); + + // Can't autoplay in these browsers so we need to use the full controls + if (requiresControls) { + html += '
"; - + var nowPlayingText = (name ? name + "\n" : "") + (seriesName || "---"); if (item.SeriesName || item.Album || item.CurrentProgram) { - html += '
' + seriesName + '
' + name + '
'; - } else { - html += '
' + name + '
' + seriesName + '
'; + nowPlayingText = (seriesName ? seriesName + "\n" : "") + "\n" + (name || "---"); } + // Fix for apostrophes and quotes + var htmlTitle = trimTitle(nowPlayingText).replace(/'/g, ''').replace(/"/g, '"'); + html += "
" + htmlTitle +
+                "
"; + html += "
" + titleHtml(nowPlayingText) + "
"; + + var nowPlayingBar = $('#nowPlayingBar'); $('.nowPlayingMediaInfo', nowPlayingBar).html(html); }; + function trunc(string, len) { + if (string.length > 0 && string.length < len) return string; + var trimmed = $.trim(string).substring(0, len).split(" ").slice(0, -1).join(" "); + if (trimmed) { + trimmed += "..."; + } else { + trimmed = "---" + } + return trimmed; + } + + function trimTitle(title) { + return title.replace("\n---", ""); + } + + function titleHtml(title) { + var titles = title.split("\n"); + return (trunc(titles[0], 30) + "
" + trunc(titles[1], 30)).replace("---", " "); + } + var getItemFields = "MediaStreams,Chapters,Path"; self.getItemsForPlayback = function (query) { @@ -1369,20 +932,6 @@ }; - self.toggleFullscreen = function () { - if (isFullScreen()) { - if (document.cancelFullScreen) { document.cancelFullScreen(); } - else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } - else if (document.webkitCancelFullScreen) { - document.webkitCancelFullScreen(); - } else { - $('.itemVideo').removeClass('fullscreenVideo'); - } - } else { - requestFullScreen(document.body); - } - }; - self.removeFromPlaylist = function (index) { self.playlist.remove(index); @@ -1511,7 +1060,7 @@ self.seek = function (position) { - changeStream(position); + self.changeStream(position); }; @@ -1519,19 +1068,28 @@ if (currentMediaElement) { currentMediaElement.volume = 0; + currentMediaElement.muted = true; + self.volumeSlider.val(0).slider('refresh'); } }; self.unmute = function () { + if (currentMediaElement) { - currentMediaElement.volume = volumeSlider.val(); + var volume = localStorage.getItem("volume") || self.volumeSlider.val(); + currentMediaElement.volume = volume; + currentMediaElement.muted = false; + self.volumeSlider.val(volume).slider('refresh'); } }; self.toggleMute = function () { if (currentMediaElement) { - currentMediaElement.volume = currentMediaElement.volume ? 0 : volumeSlider.val(); + var volume = localStorage.getItem("volume") || self.volumeSlider.val(); + currentMediaElement.volume = currentMediaElement.volume ? 0 : volume; + currentMediaElement.muted = currentMediaElement.volume == 0; + self.volumeSlider.val(volume).slider('refresh'); } }; @@ -1539,8 +1097,8 @@ if (currentMediaElement) { currentMediaElement.volume = Math.max(currentMediaElement.volume - .02, 0); - - volumeSlider.val(currentMediaElement.volume).slider('refresh'); + localStorage.setItem("volume", currentMediaElement.volume); + self.volumeSlider.val(currentMediaElement.volume).slider('refresh'); } }; @@ -1548,8 +1106,8 @@ if (currentMediaElement) { currentMediaElement.volume = Math.min(currentMediaElement.volume + .02, 1); - - volumeSlider.val(currentMediaElement.volume).slider('refresh'); + localStorage.setItem("volume", currentMediaElement.volume); + self.volumeSlider.val(currentMediaElement.volume).slider('refresh'); } }; @@ -1567,10 +1125,14 @@ }).trigger('ended'); - $('#nowPlayingBar').hide(); + var footer = $('#footer'); + footer.hide(); - if (isFullScreen()) { - exitFullScreen(); + if (elem.mediaType == "Video") { + self.resetEnhancements(); + if (self.isFullScreen()) { + self.exitFullScreen(); + } } }; @@ -1578,360 +1140,6 @@ return currentMediaElement; }; - function hideFlyout(flyout) { - - flyout.hide().empty(); - - $(document.body).off("mousedown.hidesearchhints"); - } - - function showFlyout(flyout, button) { - - $(document.body).off("mousedown.mediaflyout").on("mousedown.mediaflyout", function (e) { - - var elem = $(e.target); - - var flyoutId = flyout[0].id; - var safeItems = button + ',#' + flyoutId; - - if (!elem.is(safeItems) && !elem.parents(safeItems).length) { - hideFlyout(flyout); - } - - }); - - flyout.show(); - } - - function getChaptersFlyoutHtml(item) { - - var html = ''; - - var currentTicks = getCurrentTicks(); - - var chapters = item.Chapters || []; - - for (var i = 0, length = chapters.length; i < length; i++) { - - var chapter = chapters[i]; - - var isSelected = false; - - if (currentTicks >= chapter.StartPositionTicks) { - - var nextChapter = chapters[i + 1]; - - isSelected = !nextChapter || currentTicks < nextChapter.StartPositionTicks; - } - - if (isSelected) { - html += '
'; - } else { - html += '
'; - } - - var imgUrl; - - if (chapter.ImageTag) { - - imgUrl = ApiClient.getImageUrl(item.Id, { - maxwidth: 200, - tag: chapter.ImageTag, - type: "Chapter", - index: i - }); - - } else { - imgUrl = "css/images/media/chapterflyout.png"; - } - - html += ''; - - html += '
'; - - var name = chapter.Name || "Chapter " + (i + 1); - - html += '
' + name + '
'; - html += '
' + Dashboard.getDisplayTime(chapter.StartPositionTicks) + '
'; - - html += '
'; - - html += "
"; - } - - return html; - } - - function getAudioTracksHtml(item, cultures) { - - var streams = item.MediaStreams.filter(function (i) { - return i.Type == "Audio"; - }); - - var currentIndex = getParameterByName('AudioStreamIndex', currentMediaElement.currentSrc); - - var html = ''; - - for (var i = 0, length = streams.length; i < length; i++) { - - var stream = streams[i]; - - if (stream.Index == currentIndex) { - html += '
'; - } else { - html += '
'; - } - - html += ''; - - html += '
'; - - var language = null; - - if (stream.Language && stream.Language != "und") { - - var culture = cultures.filter(function (current) { - return current.ThreeLetterISOLanguageName.toLowerCase() == stream.Language.toLowerCase(); - }); - - if (culture.length) { - language = culture[0].DisplayName; - } - } - - html += '
' + (language || stream.Language || 'Unknown language') + '
'; - - var options = []; - - if (stream.Codec) { - options.push(stream.Codec); - } - if (stream.Profile) { - options.push(stream.Profile); - } - - if (stream.BitRate) { - options.push((Math.floor(stream.BitRate / 1000)) + ' kbps'); - } - - if (stream.Channels) { - options.push(stream.Channels + ' ch'); - } - - if (options.length) { - html += '
' + options.join(' • ') + '
'; - } - - options = []; - - if (stream.IsDefault) { - options.push('Default'); - } - if (stream.IsForced) { - options.push('Forced'); - } - - if (options.length) { - html += '
' + options.join(' • ') + '
'; - } - - html += "
"; - - html += "
"; - } - - html += '
Preferences
'; - - return html; - } - - function getSubtitleTracksHtml(item, cultures) { - - var streams = item.MediaStreams.filter(function (i) { - return i.Type == "Subtitle"; - }); - - var currentIndex = getParameterByName('SubtitleStreamIndex', currentMediaElement.currentSrc) || -1; - - var html = ''; - - streams.unshift({ - Index: -1, - Language: "Off" - }); - - for (var i = 0, length = streams.length; i < length; i++) { - - var stream = streams[i]; - - if (stream.Index == currentIndex) { - html += '
'; - } else { - html += '
'; - } - - if (stream.Index != -1) { - html += ''; - } else { - html += '
'; - } - - html += '
'; - - var language = null; - var options = []; - - if (stream.Language == "Off") { - language = "Off"; - options.push(' '); - } - else if (stream.Language && stream.Language != "und") { - - var culture = cultures.filter(function (current) { - return current.ThreeLetterISOLanguageName.toLowerCase() == stream.Language.toLowerCase(); - }); - - if (culture.length) { - language = culture[0].DisplayName; - } - } - - html += '
' + (language || 'Unknown language') + '
'; - - if (stream.Codec) { - options.push(stream.Codec); - } - - if (options.length) { - html += '
' + options.join(' • ') + '
'; - } - - options = []; - - if (stream.IsDefault) { - options.push('Default'); - } - if (stream.IsForced) { - options.push('Forced'); - } - if (stream.IsExternal) { - options.push('External'); - } - - if (options.length) { - html += '
' + options.join(' • ') + '
'; - } - - html += "
"; - - html += "
"; - } - - html += ''; - - return html; - } - - function getQualityFlyoutHtml(item) { - - var html = ''; - - var currentSrc = currentMediaElement.currentSrc.toLowerCase(); - var isStatic = currentSrc.indexOf('static=true') != -1; - - var transcodingExtension = getTranscodingExtension(); - - var currentAudioStreamIndex = getParameterByName('AudioStreamIndex', currentMediaElement.currentSrc); - - var options = getVideoQualityOptions(item, currentAudioStreamIndex, transcodingExtension); - - if (isStatic) { - options[0].name = "Direct"; - } - - for (var i = 0, length = options.length; i < length; i++) { - - var option = options[i]; - - var cssClass = "mediaFlyoutOption"; - - if (option.selected) { - cssClass += " selectedMediaFlyoutOption"; - } - - html += '
'; - - html += '
'; - - html += '
' + option.name + '
'; - - html += "
"; - - html += "
"; - } - - return html; - } - - self.showAudioTracksFlyout = function () { - - var flyout = $('#audioTracksFlyout'); - - if (!flyout.is(':visible')) { - - culturesPromise = culturesPromise || ApiClient.getCultures(); - - culturesPromise.done(function (cultures) { - - showFlyout(flyout, '#audioTracksButton'); - - flyout.html(getAudioTracksHtml(currentItem, cultures)).trigger('create').scrollTop(0); - }); - } - }; - - self.showChaptersFlyout = function () { - - var flyout = $('#chaptersFlyout'); - - if (!flyout.is(':visible')) { - - showFlyout(flyout, '#chaptersButton'); - - flyout.html(getChaptersFlyoutHtml(currentItem)).scrollTop(0); - } - }; - - self.showQualityFlyout = function () { - - var flyout = $('#qualityFlyout'); - - if (!flyout.is(':visible')) { - - showFlyout(flyout, '#qualityButton'); - - flyout.html(getQualityFlyoutHtml(currentItem)).scrollTop(0); - } - }; - - self.showSubtitleMenu = function () { - - var flyout = $('#subtitleFlyout'); - - if (!flyout.is(':visible')) { - - culturesPromise = culturesPromise || ApiClient.getCultures(); - - culturesPromise.done(function (cultures) { - - showFlyout(flyout, '#subtitleButton'); - - flyout.html(getSubtitleTracksHtml(currentItem, cultures)).trigger('create').scrollTop(0); - }); - - } - }; - self.showSendMediaMenu = function () { RemoteControl.showMenuForItem({ @@ -1939,81 +1147,6 @@ }); }; - - function enhancePlayer(video) { - var nowPlayingBar = $("#nowPlayingBar"); - mediaDomElement = $("#mediaElement", nowPlayingBar).detach(); - - $(video) - .on("click", function () { - if (this.paused) { - self.unpause(); - } else { - self.pause(); - } - }) - .on("dblclick", function () { - self.toggleFullscreen(); - }); - - changeHandler("fullscreenchange"); - changeHandler("mozfullscreenchange"); - changeHandler("webkitfullscreenchange"); - changeHandler("msfullscreenchange"); - - $(document).on("keyup.enhancePlayer", function (e) { - if (fullscreenExited) { - fullscreenExited = false; - return; - } - - if (e.keyCode == 27) { - self.stop(); - $(this).unbind("keyup.enhancePlayer"); - } - }); - - - var videoBackdrop = $("
"); - var videoPlayer = $("
") - .append(video) - .append(nowPlayingBar); - $("#footer").append(videoBackdrop.append(videoPlayer)); - - video.play(); - - isEnhanced = true; - fullscreenExited = false; - }; - - function changeHandler(event) { - document.addEventListener(event, function () { - fullscreenExited = isFullScreen() == false; - }); - } - - function resetEnhancements() { - var nowPlayingBar = $("#nowPlayingBar"); - $("#stopButton", nowPlayingBar).before(mediaDomElement); - $("#footer").append(nowPlayingBar); - $("#videoBackdrop").remove(); - }; - - function enterFullScreen() { - var player = $(".itemVideo"); - if (isEnhanced) { - player = $("#videoPlayer") - } - player.addClass("fullscreenVideo"); - }; - - function exitFullScreenToWindow() { - var player = $(".itemVideo"); - if (isEnhanced) { - player = $("#videoPlayer") - } - player.removeClass("fullscreenVideo"); - } } window.MediaPlayer = new mediaPlayer(); diff --git a/dashboard-ui/scripts/site.js b/dashboard-ui/scripts/site.js index 5996033318..a78c95fd74 100644 --- a/dashboard-ui/scripts/site.js +++ b/dashboard-ui/scripts/site.js @@ -1304,18 +1304,18 @@ $(ApiClient).on("websocketmessage", Dashboard.onWebSocketMessageReceived); $(function () { + var footerHtml = '