diff --git a/dashboard-ui/css/site.css b/dashboard-ui/css/site.css index 62933f7ac8..b26ca739ea 100644 --- a/dashboard-ui/css/site.css +++ b/dashboard-ui/css/site.css @@ -697,10 +697,15 @@ progress { /* Now playing bar */ #nowPlayingBar { - padding: 8px 20px; + padding: 6px .5em; border-top: 2px solid #D7742B; } + + #nowPlayingBar > *:not(#mediaElement) { + margin: 0 1em; + } + .nowPlayingBarImage { border: 1px solid #a7a7a7; padding: 1px; @@ -708,12 +713,12 @@ progress { } .mediaButton { - margin: 0 20px 0 0; display: inline-block; + position: relative; + top: -4px; } #mediaElement { - margin-right: 20px; display: inline-block; position: relative; } @@ -722,23 +727,27 @@ progress { width: 270px; } -#nowPlayingBar #mediaInfo, #nowPlayingBar #mediaInfo div { - margin-left: 10px; +.nowPlayingMediaInfo div { display: inline-block; } -#nowPlayingBar #mediaInfo { +.nowPlayingMediaInfo a { + margin-right: .25em; +} + + +.nowPlayingMediaInfo { display: none; } @media all and (min-width: 650px) { - #nowPlayingBar #mediaInfo { + .nowPlayingMediaInfo { display: inline-block; } } .mediaButton img { - height: 28px; + height: 24px; } .itemVideo, .itemVideo.video-js { @@ -749,6 +758,45 @@ progress { bottom: -5px; } +.currentTime { + display: inline-block; + position: relative; + top: -10px; +} + +.mediaSlider { + -webkit-appearance: none; + -moz-apperance: none; + background: #777; + border-radius: 5px; + vertical-align: bottom; + position: relative; + top: -17px; + height: 3px; + width: 50px; +} + + .mediaSlider::-webkit-slider-thumb { + -webkit-appearance: none; + -moz-apperance: none; + width: 15px; + height: 15px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + -ms-border-radius: 10px; + -o-border-radius: 10px; + border-radius: 10px; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fefefe), color-stop(0.49, #dddddd), color-stop(0.51, #d1d1d1), color-stop(1, #a1a1a1) ); + } + +.positionSlider { + width: 130px; +} + +.volumeButton { + margin-right: .5em!important; +} + @media all and (min-width: 650px) { .itemVideo { diff --git a/dashboard-ui/scripts/mediaplayer.js b/dashboard-ui/scripts/mediaplayer.js index b2467c9e7d..414cd8c4ed 100644 --- a/dashboard-ui/scripts/mediaplayer.js +++ b/dashboard-ui/scripts/mediaplayer.js @@ -1,4 +1,4 @@ -(function (document, clearTimeout, screen, localStorage, _V_, $, setInterval, window) { +(function (document, setTimeout, clearTimeout, screen, localStorage, _V_, $, setInterval, window) { function mediaPlayer() { var self = this; @@ -7,14 +7,149 @@ var testableVideoElement = document.createElement('video'); var currentMediaElement; var currentProgressInterval; + var positionSlider; + var isPositionSliderActive; + var currentTimeElement; + var currentItem; + var volumeSlider; + var muteButton; + var unmuteButton; + var startTimeTicksOffset; + var curentDurationTicks; + var isStaticStream; - if (typeof(self.playing) == 'undefined') { - self.playing = ''; - } + self.playing = ''; + self.queue = []; - if (typeof(self.queue) == 'undefined') { - self.queue = []; - } + function replaceQueryString(url, param, value) { + var re = new RegExp("([?|&])" + param + "=.*?(&|$)", "i"); + if (url.match(re)) + return url.replace(re, '$1' + param + "=" + value + '$2'); + else + return url + '&' + param + "=" + value; + } + + function updateVolumeButtons(vol) { + + if (vol) { + muteButton.show(); + unmuteButton.hide(); + } else { + muteButton.hide(); + unmuteButton.show(); + } + } + + function onPlaybackStopped() { + + currentTimeElement.hide(); + + var endTime = this.currentTime; + + this.currentTime = 0; + + clearProgressInterval(); + + var position = Math.floor(10000000 * endTime) + startTimeTicksOffset; + + ApiClient.reportPlaybackStopped(Dashboard.getCurrentUserId(), currentItem.Id, position); + + MediaPlayer.queuePlayNext(); + } + + function startProgressInterval(itemId) { + + clearProgressInterval(); + + var intervalTime = ApiClient.isWebSocketOpen() ? 10000 : 30000; + + currentProgressInterval = setInterval(function () { + + var position = Math.floor(10000000 * currentMediaElement.currentTime) + startTimeTicksOffset; + + ApiClient.reportPlaybackProgress(Dashboard.getCurrentUserId(), itemId, position); + + }, intervalTime); + } + + function clearProgressInterval() { + + if (currentProgressInterval) { + clearTimeout(currentProgressInterval); + currentProgressInterval = null; + } + } + + $(function () { + + muteButton = $('#muteButton'); + unmuteButton = $('#unmuteButton'); + + currentTimeElement = $('.currentTime'); + + volumeSlider = $('.volumeSlider').on('change', function () { + + var vol = this.value; + updateVolumeButtons(vol); + currentMediaElement.volume = vol; + }); + + positionSlider = $(".positionSlider").on('change', function () { + + isPositionSliderActive = true; + + setCurrentTimePercent(parseInt(this.value), currentItem); + + }).on('changed', function () { + + isPositionSliderActive = false; + + var element = currentMediaElement; + + var newPercent = parseInt(this.value); + + var newPositionTicks = (newPercent / 100) * currentItem.RunTimeTicks; + + if (isStaticStream) { + + element.currentTime = newPositionTicks / (1000 * 10000); + + } else { + + var currentSrc = element.currentSrc; + + if (currentSrc.toLowerCase().indexOf('starttimeticks') == -1) { + + currentSrc += "&starttimeticks=" + newPositionTicks; + + } else { + currentSrc = replaceQueryString(currentSrc, 'starttimeticks', newPositionTicks); + } + + clearProgressInterval(); + + $(element).off('ended.playbackstopped').on("play.onceafterseek", function () { + + $(this).off('play.onceafterseek').on('ended.playbackstopped', onPlaybackStopped); + startProgressInterval(currentItem.Id); + + }); + startTimeTicksOffset = newPositionTicks; + + element.src = currentSrc; + } + }); + + (function (el, timeout) { + var timer, trig = function () { el.trigger("changed"); }; + el.bind("change", function () { + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(trig, timeout); + }); + })(positionSlider, 500); + }); function endsWith(text, pattern) { @@ -25,9 +160,35 @@ return d >= 0 && text.lastIndexOf(pattern) === d; } - function playAudio(item, params) { + function setCurrentTimePercent(percent, item) { - var volume = localStorage.getItem("volume") || 0.5; + var position = (percent / 100) * curentDurationTicks; + setCurrentTime(position, item, false); + } + + function setCurrentTime(ticks, item, updateSlider) { + + // Convert to ticks + ticks = Math.floor(ticks); + + var timeText = DashboardPage.getDisplayText(ticks); + + if (curentDurationTicks) { + + timeText += " / " + DashboardPage.getDisplayText(curentDurationTicks); + + if (updateSlider) { + var percent = ticks / curentDurationTicks; + percent *= 100; + + positionSlider.val(percent); + } + } + + currentTimeElement.html(timeText); + } + + function playAudio(item, params) { var baseParams = { audioChannels: 2, @@ -68,58 +229,82 @@ } - /* ffmpeg always says the ogg stream is corrupt after conversion - var oggUrl = ApiClient.getUrl('Audio/' + item.Id + '/stream.oga', $.extend({}, baseParams, { - audioCodec: 'Vorbis' - })); - */ - var html = ''; - html += '