diff --git a/dashboard-ui/css/images/media/audioflyout.png b/dashboard-ui/css/images/media/audioflyout.png new file mode 100644 index 0000000000..cd13df0978 Binary files /dev/null and b/dashboard-ui/css/images/media/audioflyout.png differ diff --git a/dashboard-ui/css/images/media/subtitleflyout.png b/dashboard-ui/css/images/media/subtitleflyout.png new file mode 100644 index 0000000000..821005c50c Binary files /dev/null and b/dashboard-ui/css/images/media/subtitleflyout.png differ diff --git a/dashboard-ui/css/site.css b/dashboard-ui/css/site.css index 509f2aabdf..4c0b2ba12a 100644 --- a/dashboard-ui/css/site.css +++ b/dashboard-ui/css/site.css @@ -663,7 +663,6 @@ progress { progress::-moz-progress-bar { border-radius: 5px; background-image: -moz-linear-gradient( center bottom, rgb(43,194,83) 37%, rgb(84,240,84) 69% ); - s; } /* Chrome */ @@ -894,6 +893,10 @@ input[type="range"]::-ms-fill-upper { width: 250px; } +.audioTracksFlyout { + width: 250px; +} + .mediaFlyoutOption { display: block; text-decoration: none; @@ -912,13 +915,21 @@ input[type="range"]::-ms-fill-upper { .mediaFlyoutOptionImage { display: inline-block; - width: 40%; + width: 15%; vertical-align: middle; } .mediaFlyoutOptionImage + .mediaFlyoutOptionContent { vertical-align: top; display: inline-block; + width: 85%; + } + +.chaptersFlyout .mediaFlyoutOptionImage { + width: 40%; +} + + .chaptersFlyout .mediaFlyoutOptionImage + .mediaFlyoutOptionContent { width: 60%; } diff --git a/dashboard-ui/scripts/extensions.js b/dashboard-ui/scripts/extensions.js index 753c59b0a9..6e06d47d4f 100644 --- a/dashboard-ui/scripts/extensions.js +++ b/dashboard-ui/scripts/extensions.js @@ -188,11 +188,11 @@ function humane_elapsed(firstDateStr, secondDateStr) { } -function getParameterByName(name) { +function getParameterByName(name, url) { name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]"); var regexS = "[\\?&]" + name + "=([^&#]*)"; var regex = new RegExp(regexS); - var results = regex.exec(window.location.search); + var results = regex.exec(url || window.location.search); if (results == null) return ""; else diff --git a/dashboard-ui/scripts/mediaplayer.js b/dashboard-ui/scripts/mediaplayer.js index fcea5e514e..56e2200bda 100644 --- a/dashboard-ui/scripts/mediaplayer.js +++ b/dashboard-ui/scripts/mediaplayer.js @@ -18,6 +18,7 @@ var startTimeTicksOffset; var curentDurationTicks; var isStaticStream; + var culturesPromise; self.playing = ''; self.queue = []; @@ -69,6 +70,10 @@ } } + function getCurrentTicks(mediaElement) { + return Math.floor(10000000 * (mediaElement || currentMediaElement).currentTime) + startTimeTicksOffset; + } + function onPlaybackStopped() { currentTimeElement.hide(); @@ -102,9 +107,8 @@ } function sendProgressUpdate(itemId) { - var position = Math.floor(10000000 * currentMediaElement.currentTime) + startTimeTicksOffset; - ApiClient.reportPlaybackProgress(Dashboard.getCurrentUserId(), itemId, position); + ApiClient.reportPlaybackProgress(Dashboard.getCurrentUserId(), itemId, getCurrentTicks()); } function clearProgressInterval() { @@ -115,24 +119,27 @@ } } - function seek(ticks) { + function changeStream(ticks, params) { var element = currentMediaElement; - if (isStaticStream) { + if (isStaticStream && params == null) { element.currentTime = ticks / (1000 * 10000); } else { + params = params || {}; + var currentSrc = element.currentSrc; - if (currentSrc.toLowerCase().indexOf('starttimeticks') == -1) { + currentSrc = replaceQueryString(currentSrc, 'starttimeticks', ticks); - currentSrc += "&starttimeticks=" + ticks; - - } else { - currentSrc = replaceQueryString(currentSrc, 'starttimeticks', ticks); + if (params.AudioStreamIndex != null) { + currentSrc = replaceQueryString(currentSrc, 'AudioStreamIndex', params.AudioStreamIndex); + } + if (params.SubtitleStreamIndex != null) { + currentSrc = replaceQueryString(currentSrc, 'SubtitleStreamIndex', params.SubtitleStreamIndex); } clearProgressInterval(); @@ -158,7 +165,7 @@ var newPositionTicks = (newPercent / 100) * currentItem.RunTimeTicks; - seek(newPositionTicks); + changeStream(newPositionTicks); } $(function () { @@ -213,10 +220,32 @@ var ticks = parseInt(this.getAttribute('data-positionticks')); - seek(ticks); - + changeStream(ticks); + hideFlyout($('#chaptersFlyout')); }); + + $('#audioTracksFlyout').on('click', '.mediaFlyoutOption', function () { + + if (!$(this).hasClass('selectedMediaFlyoutOption')) { + var index = parseInt(this.getAttribute('data-index')); + + changeStream(getCurrentTicks(), { AudioStreamIndex: index }); + } + + hideFlyout($('#audioTracksFlyout')); + }); + + $('#subtitleFlyout').on('click', '.mediaFlyoutOption', function () { + + if (!$(this).hasClass('selectedMediaFlyoutOption')) { + var index = parseInt(this.getAttribute('data-index')); + + changeStream(getCurrentTicks(), { SubtitleStreamIndex: index }); + } + + hideFlyout($('#subtitleFlyout')); + }); }); function endsWith(text, pattern) { @@ -385,9 +414,7 @@ if (!isPositionSliderActive) { - var ticks = startTimeTicksOffset + this.currentTime * 1000 * 10000; - - setCurrentTime(ticks, item, true); + setCurrentTime(getCurrentTicks(this), item, true); } }).on("ended.playbackstopped", onPlaybackStopped); @@ -484,6 +511,7 @@ $('#mediaElement', nowPlayingBar).html(html); $('#qualityButton', nowPlayingBar).show(); + $('#audioTracksButton', nowPlayingBar).show(); if (item.MediaStreams.filter(function (i) { @@ -552,9 +580,7 @@ if (!isPositionSliderActive) { - var ticks = startTimeTicksOffset + this.currentTime * 1000 * 10000; - - setCurrentTime(ticks, item, true); + setCurrentTime(getCurrentTicks(this), item, true); } }).on("ended.playbackstopped", onPlaybackStopped); @@ -569,10 +595,14 @@ function getInitialAudioStreamIndex(mediaStreams, user) { + var audioStreams = mediaStreams.filter(function (stream) { + return stream.Type == "Audio"; + }); + if (user.Configuration.AudioLanguagePreference) { - for (var i = 0, length = mediaStreams.length; i < length; i++) { - var mediaStream = mediaStreams[i]; + for (var i = 0, length = audioStreams.length; i < length; i++) { + var mediaStream = audioStreams[i]; if (mediaStream.Type == "Audio" && mediaStream.Language == user.Configuration.AudioLanguagePreference) { return mediaStream.Index; @@ -581,7 +611,8 @@ } } - return null; + // Just use the first audio stream + return audioStreams.length ? audioStreams[0].Index : null; } function getInitialSubtitleStreamIndex(mediaStreams, user) { @@ -951,8 +982,8 @@ var html = ''; - var currentTicks = Math.floor(10000000 * currentMediaElement.currentTime) + startTimeTicksOffset; - + var currentTicks = getCurrentTicks(); + for (var i = 0, length = item.Chapters.length; i < length; i++) { var chapter = item.Chapters[i]; @@ -1004,14 +1035,174 @@ 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 || 'Unknown language') + '
'; + + var options = []; + + if (stream.Codec) { + options.push(stream.Codec); + } + if (stream.Profile) { + options.push(stream.Profile); + } + + if (stream.BitRate) { + options.push((parseInt(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 += "
"; + } + + return html; + } + + function getSubtitleTracksHtml(item, cultures) { + + var streams = item.MediaStreams.filter(function (i) { + return i.Type == "Subtitle"; + }); + + var currentIndex = getParameterByName('SubtitleStreamIndex', 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 || 'Unknown language') + '
'; + + var options = []; + + 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 += "
"; + } + + return html; + } + self.showAudioTracksFlyout = function () { var flyout = $('#audioTracksFlyout'); if (!flyout.is(':visible')) { - showFlyout(flyout, '#audioTracksButton'); + culturesPromise = culturesPromise || ApiClient.getCultures(); + culturesPromise.done(function (cultures) { + + showFlyout(flyout, '#audioTracksButton'); + + flyout.html(getAudioTracksHtml(currentItem, cultures)).scrollTop(0); + }); } }; @@ -1023,7 +1214,7 @@ showFlyout(flyout, '#chaptersButton'); - flyout.html(getChaptersFlyoutHtml(currentItem)); + flyout.html(getChaptersFlyoutHtml(currentItem)).scrollTop(0); } }; @@ -1044,7 +1235,14 @@ if (!flyout.is(':visible')) { - showFlyout(flyout, '#subtitleButton'); + culturesPromise = culturesPromise || ApiClient.getCultures(); + + culturesPromise.done(function (cultures) { + + showFlyout(flyout, '#subtitleButton'); + + flyout.html(getSubtitleTracksHtml(currentItem, cultures)).scrollTop(0); + }); } }; diff --git a/dashboard-ui/scripts/site.js b/dashboard-ui/scripts/site.js index 94c61592ac..0054f3db29 100644 --- a/dashboard-ui/scripts/site.js +++ b/dashboard-ui/scripts/site.js @@ -1118,19 +1118,20 @@ $(function () { footerHtml += ''; footerHtml += ''; footerHtml += ''; - footerHtml += ''; footerHtml += ''; - footerHtml += '
'; + footerHtml += '
'; footerHtml += ''; - footerHtml += '
'; + footerHtml += '
'; footerHtml += ''; - footerHtml += '
'; + footerHtml += '
'; footerHtml += ''; - footerHtml += '
'; + footerHtml += '
'; + + footerHtml += ''; footerHtml += '
';