diff --git a/dashboard-ui/css/mediaplayer-video.css b/dashboard-ui/css/mediaplayer-video.css index 49e3855e5e..f3fc57bcdb 100644 --- a/dashboard-ui/css/mediaplayer-video.css +++ b/dashboard-ui/css/mediaplayer-video.css @@ -111,7 +111,7 @@ #mediaPlayer .ui-slider-handle { height: 12px !important; - margin-top: -7px !important; + margin-top: -7px !important; } #videoPlayer.fullscreenVideo #videoControls { @@ -212,10 +212,6 @@ /****************************************/ @media all and (max-width: 1200px), all and (max-height: 720px) { - #videoControls .nowPlayingMediaInfo { - display: none; - } - #videoControls .currentTime { margin-right: 0; min-width: 120px; @@ -370,4 +366,87 @@ .cursor-inactive { cursor: none; -} \ No newline at end of file +} + +.mediaFlyoutContainer { + display: inline-block; +} + +.mediaPlayerFlyout { + width: 200px; + color: #000; + background-color: #fff; + border: 1px solid #999; + position: absolute; + z-index: 99999; + bottom: 78px; + margin-left: -125px; + max-height: 300px; + overflow-y: auto; + font-size: 13px; +} + +.chaptersFlyout, .audioTracksFlyout { + width: 250px; + margin-left: -150px; +} + +.mediaFlyoutOption { + display: block; + text-decoration: none; + color: #000; + border-bottom: 1px solid #eee; + cursor: pointer; +} + + .mediaFlyoutOption:hover, .mediaFlyoutOption:focus { + background-color: #eee; + } + +.selectedMediaFlyoutOption { + background-color: #d9F4FF; + background-image: url(images/media/selected.png); + background-repeat: no-repeat; + background-position: right top; + background-size: 16px 16px; +} + +.mediaFlyoutOptionImage { + display: inline-block; + width: 15%; + vertical-align: middle; +} + + .mediaFlyoutOptionImage + .mediaFlyoutOptionContent { + vertical-align: top; + display: inline-block; + width: 85%; + } + +.chaptersFlyout .mediaFlyoutOptionImage { + width: 40%; +} + + .chaptersFlyout .mediaFlyoutOptionImage + .mediaFlyoutOptionContent { + width: 60%; + } + +.mediaFlyoutOptionName { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + padding-left: 5px; + font-size: 13px; + font-weight: normal; +} + +.mediaFlyoutOptionSecondaryText { + font-size: 13px; + color: #333; + font-weight: normal; + margin-top: 3px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + padding-left: 5px; +} diff --git a/dashboard-ui/css/mediaplayer.css b/dashboard-ui/css/mediaplayer.css index c1a1e176db..c44552e70c 100644 --- a/dashboard-ui/css/mediaplayer.css +++ b/dashboard-ui/css/mediaplayer.css @@ -1,21 +1,9 @@ /* Now playing bar */ .nowPlayingBar { - padding: 6px 0 20px 0; + padding: 10px 0 14px 0; border-top: 2px solid green; } - .nowPlayingBar .barBackground { - border-top: 2px solid green; - position: absolute; - margin: -8px -0.5em -22px !important; - width: 100%; - height: 100%; - } - - .nowPlayingBar > *:not(#mediaElement):not(.mediaFlyoutContainer):not(.barBackground ) { - position: relative; - } - .nowPlayingBarImage { vertical-align: bottom; } @@ -29,27 +17,32 @@ position: relative; } -.nowPlayingMediaInfo div { - display: inline-block; -} - -.nowPlayingMediaInfo a { - margin-right: .25em; -} - - -.nowPlayingMediaInfo { - display: inline-block; -} - .nowPlayingText { - position: relative; - top: -3px; - margin-left: 3px; - margin-right: 2em; + display: inline-block; font-weight: normal; + position: relative; + top: -7px; + margin-left: 3px; + max-width: 200px; + overflow: hidden; + margin-right: 2em; + white-space: nowrap; } +.nowPlayingDoubleText { + top: 0; +} + +.nowPlayingImage { + display: inline-block; +} + + .nowPlayingImage img { + height: 40px; + margin-right: .5em; + vertical-align: bottom; + } + .mediaButton img { height: 24px; } @@ -60,7 +53,6 @@ top: -10px; max-width: 110px; margin-right: 2em; - font-size: 14px; font-weight: normal; } @@ -127,125 +119,27 @@ input[type="range"]::-ms-fill-upper { } @media all and (max-width: 800px) { - #nowPlayingBar .volumeButton, #nowPlayingBar .volumeSliderContainer, #nowPlayingBar .muteButton, #nowPlayingBar .unmuteButton, #nowPlayingBar .nowPlayingMediaInfo { - display: none; /*!important;*/ - } -} -@media all and (max-width: 650px) { - #nowPlayingBar .nowPlayingMediaInfo { - display: none; - } -} - -@media all and (max-width: 600px) { - #nowPlayingBar .chaptersButton, #nowPlayingBar .audioTracksButton { - display: none; /*!important;*/ + #nowPlayingBar .mediaButton { + margin-top: 0; + margin-bottom: 0; } - #nowPlayingBar .positionSliderContainer, #nowPlayingBar .currentTime { - top: 0!important; - position: relative!important; - } -} + #nowPlayingBar .mediaButton:not(#playButton):not(#pauseButton) { + display: none; + } -@media all and (max-width: 500px) { - #nowPlayingBar .positionSliderContainer { - width: 80px; + #nowPlayingBar #playButton, #nowPlayingBar #pauseButton { + float: right; } - #nowPlayingBar .previousTrackButton, #nowPlayingBar .nextTrackButton { - display: none; /*!important;*/ - } -} - -@media all and (max-width: 400px) { - #nowPlayingBar .playlistButton { - display: none; /*!important;*/ - } -} - -.mediaFlyoutContainer { - display: inline-block; -} - -.mediaPlayerFlyout { - width: 200px; - color: #000; - background-color: #fff; - border: 1px solid #999; - position: absolute; - z-index: 99999; - bottom: 78px; - margin-left: -125px; - max-height: 300px; - overflow-y: auto; - font-size: 13px; -} - -.chaptersFlyout,.audioTracksFlyout { - width: 250px; - margin-left: -150px; -} - -.mediaFlyoutOption { - display: block; - text-decoration: none; - color: #000; - border-bottom: 1px solid #eee; - cursor: pointer; -} - - .mediaFlyoutOption:hover, .mediaFlyoutOption:focus { - background-color: #eee; + #nowPlayingBar .currentTime, #nowPlayingBar .positionSliderContainer, #nowPlayingBar .volumeSliderContainer, #nowPlayingBar .muteButton, #nowPlayingBar .unmuteButton { + display: none !important; } -.selectedMediaFlyoutOption { - background-color: #d9F4FF; - background-image: url(images/media/selected.png); - background-repeat: no-repeat; - background-position: right top; - background-size: 16px 16px; -} - -.mediaFlyoutOptionImage { - display: inline-block; - width: 15%; - vertical-align: middle; -} - - .mediaFlyoutOptionImage + .mediaFlyoutOptionContent { - vertical-align: top; - display: inline-block; - width: 85%; + .nowPlayingBar { + padding: 10px 5px 10px 0; } - -.chaptersFlyout .mediaFlyoutOptionImage { - width: 40%; -} - - .chaptersFlyout .mediaFlyoutOptionImage + .mediaFlyoutOptionContent { - width: 60%; - } - -.mediaFlyoutOptionName { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - padding-left: 5px; - font-size: 13px; - font-weight: normal; -} - -.mediaFlyoutOptionSecondaryText { - font-size: 13px; - color: #333; - font-weight: normal; - margin-top: 3px; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - padding-left: 5px; } @media (min-width: 1440px) { @@ -253,3 +147,16 @@ input[type="range"]::-ms-fill-upper { width: 300px; } } + +.mediaPlayerAudioContainer { + position: fixed; + top: 40%; + text-align: center; + left: 0; + right: 0; +} + +.mediaPlayerAudioContainerInner { + padding: 1em; + background: #222; +} diff --git a/dashboard-ui/scripts/chromecast.js b/dashboard-ui/scripts/chromecast.js index 4001400a33..792185d9be 100644 --- a/dashboard-ui/scripts/chromecast.js +++ b/dashboard-ui/scripts/chromecast.js @@ -241,6 +241,7 @@ }; function getCodecLimits() { + return { maxVideoAudioChannels: 6, @@ -608,7 +609,7 @@ url += '&maxheight=' + codecLimits.maxHeight; url += '&videoCodec=h264'; - url += '&audioCodec=aac'; + url += '&audioCodec=aac,mp3'; url += '&audiosamplerate=' + codecLimits.maxSampleRate; url += '&mediasourceid=' + mediaSourceInfo.mediaSource.Id; @@ -1004,7 +1005,7 @@ var castPlayer = new CastPlayer(); - function chromecastPlayer(castPlayer) { + function chromecastPlayer() { var self = this; @@ -1018,11 +1019,11 @@ self.playlistIndex = 0; - $.subscribe("/playback/complete", function (e) { - if (self.playlistIndex < (self.playlist.items || []).length) { - self.play(self.playlist); - } - }); + //$.subscribe("/playback/complete", function (e) { + // if (self.playlistIndex < (self.playlist.items || []).length) { + // self.play(self.playlist); + // } + //}); ////$(".positionSlider", "#footer").off("slidestart slidestop") //// .on('slidestart', function (e) { @@ -1043,13 +1044,13 @@ query.ExcludeLocationTypes = "Virtual"; return ApiClient.getItems(userId, query); - }; + } function queueItems (items) { for (var i = 0, length = items.length; i < length; i++) { self.playlist.push(items[i]); } - }; + } function queueItemsNext(items) { var insertIndex = 1; @@ -1057,7 +1058,7 @@ self.playlist.splice(insertIndex, 0, items[i]); insertIndex++; } - }; + } function translateItemsForPlayback(items) { var deferred = $.Deferred(); @@ -1100,7 +1101,7 @@ } return deferred.promise(); - }; + } self.play = function (options) { console.log("play", options); @@ -1371,7 +1372,7 @@ }; } - MediaController.registerPlayer(new chromecastPlayer(castPlayer)); + MediaController.registerPlayer(new chromecastPlayer()); $(MediaController).on('playerchange', function () { diff --git a/dashboard-ui/scripts/edititempeople.js b/dashboard-ui/scripts/edititempeople.js index 6c026f4837..91a4ee6822 100644 --- a/dashboard-ui/scripts/edititempeople.js +++ b/dashboard-ui/scripts/edititempeople.js @@ -57,7 +57,7 @@ if (person.PrimaryImageTag) { imgUrl = ApiClient.getPersonImageUrl(person.Name, { - height: 280, + width: 150, tag: person.PrimaryImageTag, type: "primary" }); diff --git a/dashboard-ui/scripts/itemdetailpage.js b/dashboard-ui/scripts/itemdetailpage.js index a6402773f1..19c4e97ec3 100644 --- a/dashboard-ui/scripts/itemdetailpage.js +++ b/dashboard-ui/scripts/itemdetailpage.js @@ -1238,7 +1238,7 @@ if (cast.PrimaryImageTag) { imgUrl = ApiClient.getPersonImageUrl(cast.Name, { - width: 130, + width: 100, tag: cast.PrimaryImageTag, type: "primary" }); diff --git a/dashboard-ui/scripts/librarymenu.js b/dashboard-ui/scripts/librarymenu.js index 9610733899..6b9e03168a 100644 --- a/dashboard-ui/scripts/librarymenu.js +++ b/dashboard-ui/scripts/librarymenu.js @@ -31,7 +31,7 @@ if (user.PrimaryImageTag) { var url = ApiClient.getUserImageUrl(user.Id, { - height: 40, + height: 24, tag: user.PrimaryImageTag, type: "Primary" }); diff --git a/dashboard-ui/scripts/mediacontroller.js b/dashboard-ui/scripts/mediacontroller.js index c755df654f..74672e59a0 100644 --- a/dashboard-ui/scripts/mediacontroller.js +++ b/dashboard-ui/scripts/mediacontroller.js @@ -175,6 +175,11 @@ })[0]; }; + self.getCurrentPlayer = function () { + + return currentPlayer; + }; + self.pause = function () { currentPlayer.pause(); }; @@ -188,7 +193,7 @@ }; self.seek = function (position) { - currentPlayer.changeStream(position); + currentPlayer.seek(position); }; self.currentPlaylistIndex = function (i) { diff --git a/dashboard-ui/scripts/mediaplayer-video.js b/dashboard-ui/scripts/mediaplayer-video.js index 1a7395a0a0..ba7fef777d 100644 --- a/dashboard-ui/scripts/mediaplayer-video.js +++ b/dashboard-ui/scripts/mediaplayer-video.js @@ -23,6 +23,13 @@ var idleState = true; var remoteFullscreen = false; + var muteButton = null; + var unmuteButton = null; + var volumeSlider = null; + var positionSlider; + var isPositionSliderActive; + var currentTimeElement; + self.initVideoPlayer = function () { video = playVideo(item, mediaSource, startPosition, user); return video; @@ -66,7 +73,7 @@ self.resetEnhancements = function () { $("#mediaPlayer").hide(); $('#videoPlayer').removeClass('fullscreenVideo'); - $("#videoControls").removeClass("inactive") + $("#videoControls").removeClass("inactive"); $("video").remove(); $("html").css("cursor", "default"); $(".ui-loader").hide(); @@ -155,7 +162,38 @@ } }); + function onPositionSliderChange() { + + isPositionSliderActive = false; + + var newPercent = parseInt(this.value); + + var newPositionTicks = (newPercent / 100) * currentMediaSource.RunTimeTicks; + + self.changeStream(Math.floor(newPositionTicks)); + } + $(function () { + + var parent = $("#mediaPlayer"); + muteButton = $('.muteButton', parent); + unmuteButton = $('.unmuteButton', parent); + currentTimeElement = $('.currentTime', parent); + + positionSlider = $(".positionSlider", parent).on('slidestart', function (e) { + + isPositionSliderActive = true; + + }).on('slidestop', onPositionSliderChange); + + volumeSlider = $('.volumeSlider', parent).on('slidestop', function () { + + var vol = this.value; + + updateVolumeButtons(vol); + self.setVolume(vol * 100); + }); + $('#video-chaptersFlyout').on('click', '.mediaFlyoutOption', function () { var ticks = parseInt(this.getAttribute('data-positionticks')); @@ -264,6 +302,17 @@ }, 4000); } + function updateVolumeButtons(vol) { + + if (vol) { + muteButton.show(); + unmuteButton.hide(); + } else { + muteButton.hide(); + unmuteButton.show(); + } + } + function requestFullScreen(element) { // Supports most browsers and their versions. @@ -729,7 +778,7 @@ return options; } - + function playVideo(item, mediaSource, startPosition, user) { var mediaStreams = mediaSource.MediaStreams || []; @@ -771,7 +820,10 @@ videoBitrate: mp4Quality.videoBitrate, audioBitrate: mp4Quality.audioBitrate, VideoCodec: mp4Quality.videoCodec, - AudioCodec: mp4Quality.audioCodec + AudioCodec: mp4Quality.audioCodec, + + // None of the browsers seem to like this + EnableAutoStreamCopy: false })) + seekParam; @@ -781,7 +833,8 @@ AudioCodec: 'Vorbis', maxWidth: webmQuality.maxWidth, videoBitrate: webmQuality.videoBitrate, - audioBitrate: webmQuality.audioBitrate + audioBitrate: webmQuality.audioBitrate, + EnableAutoStreamCopy: false })) + seekParam; @@ -874,42 +927,26 @@ var video = $("video", videoElement); - initialVolume = localStorage.getItem("volume") || 0.5; + initialVolume = self.getSavedVolume(); video.each(function () { this.volume = initialVolume; }); - self.volumeSlider.val(initialVolume).slider('refresh'); - self.updateVolumeButtons(initialVolume); + volumeSlider.val(initialVolume).slider('refresh'); + updateVolumeButtons(initialVolume); video.on("volumechange", function (e) { - var muted = this.muted; - var vol = this.volume; - if (!muted && this.volume > 0) { - localStorage.setItem("volume", vol); - } - - this.muted = this.volume == 0; - - self.updateVolumeButtons(vol); - - }).on("play.once", function () { - - video.off("play.once"); + updateVolumeButtons(vol); }).on("playing.once", function () { - self.updateCanClientSeek(this); - video.off("playing.once"); - ApiClient.reportPlaybackStart(Dashboard.getCurrentUserId(), item.Id, mediaSource.Id, true, item.MediaType); - - self.startProgressInterval(item.Id, mediaSource.Id); + self.onPlaybackStart(this, item, mediaSource); }).on("pause", function (e) { @@ -939,9 +976,9 @@ }).on("timeupdate", function () { - if (!self.isPositionSliderActive) { + if (!isPositionSliderActive) { - self.setCurrentTime(self.getCurrentTicks(this), item, true); + self.setCurrentTime(self.getCurrentTicks(this), positionSlider, currentTimeElement); } }).on("error", function () { @@ -995,8 +1032,13 @@ $(".ui-loader").hide(); $("html").css("cursor", "default"); - }).on("ended.playbackstopped", self.onPlaybackStopped) - .on('ended.playnext', self.playNextAfterEnded); + }).on("ended.playbackstopped", function () { + + currentTimeElement.empty(); + + self.onPlaybackStopped.call(this); + + }).on('ended.playnext', self.playNextAfterEnded); // Stop playback on browser back button nav $(window).on("popstate", function () { diff --git a/dashboard-ui/scripts/mediaplayer.js b/dashboard-ui/scripts/mediaplayer.js index 516c10cde1..37da88a433 100644 --- a/dashboard-ui/scripts/mediaplayer.js +++ b/dashboard-ui/scripts/mediaplayer.js @@ -13,12 +13,6 @@ var currentPlaylistIndex = 0; self.currentDurationTicks = null; - self.currentTimeElement = null; - self.unmuteButton = null; - self.muteButton = null; - self.positionSlider = null; - self.isPositionSliderActive = null; - self.volumeSlider = null; self.startTimeTicksOffset = null; self.playlist = []; @@ -45,22 +39,11 @@ canClientSeek = duration && !isNaN(duration) && duration != Number.POSITIVE_INFINITY && duration != Number.NEGATIVE_INFINITY; }; - self.updateVolumeButtons = function (vol) { - - if (vol) { - self.muteButton.show().prop("disabled", false); - self.unmuteButton.hide().prop("disabled", true); - } else { - self.muteButton.hide().prop("disabled", true); - self.unmuteButton.show().prop("disabled", false); - } - }; - self.getCurrentTicks = function (mediaElement) { return Math.floor(10000000 * (mediaElement || currentMediaElement).currentTime) + self.startTimeTicksOffset; }; - self.clearPauseStop = function() { + self.clearPauseStop = function () { if (self.pauseStop) { console.log('clearing pause stop timer'); @@ -69,31 +52,6 @@ } }; - self.onPlaybackStopped = function () { - - self.clearPauseStop(); - - $(this).off('ended.playbackstopped'); - - self.currentTimeElement.empty(); - - var endTime = this.currentTime; - - clearProgressInterval(); - - var position = Math.floor(10000000 * endTime) + self.startTimeTicksOffset; - - ApiClient.reportPlaybackStopped(Dashboard.getCurrentUserId(), currentItem.Id, currentMediaSource.Id, position); - - if (currentItem.MediaType == "Video") { - ApiClient.stopActiveEncodings(); - if (self.isFullScreen()) { - self.exitFullScreen(); - } - self.resetEnhancements(); - } - }; - self.playNextAfterEnded = function () { $(this).off('ended.playnext'); @@ -210,7 +168,7 @@ } }; - self.setCurrentTime = function (ticks, item, updateSlider) { + self.setCurrentTime = function (ticks, positionSlider, currentTimeElement) { // Convert to ticks ticks = Math.floor(ticks); @@ -221,17 +179,28 @@ timeText += " / " + Dashboard.getDisplayTime(self.currentDurationTicks); - if (updateSlider) { + if (positionSlider) { + var percent = ticks / self.currentDurationTicks; percent *= 100; - self.positionSlider.val(percent).slider('enable').slider('refresh'); + positionSlider.val(percent).slider('enable').slider('refresh'); } } else { - self.positionSlider.slider('disable').slider('refresh'); + + if (positionSlider) { + + positionSlider.slider('disable').slider('refresh'); + } } - self.currentTimeElement.html(timeText); + if (currentTimeElement) { + currentTimeElement.html(timeText); + } + + var state = self.getPlayerState(currentMediaElement, currentItem, currentMediaSource); + + $(self).trigger('positionchange', [state]); }; self.canPlayVideoDirect = function (mediaSource, videoStream, audioStream, subtitleStream, maxWidth, bitrate) { @@ -459,8 +428,6 @@ var mediaElement; - var mediaControls = $('#nowPlayingBar'); - if (item.MediaType === "Video") { currentItem = item; @@ -470,15 +437,12 @@ mediaElement = self.initVideoPlayer(); self.currentDurationTicks = currentMediaSource.RunTimeTicks; - mediaControls = $("#videoControls"); - } else if (item.MediaType === "Audio") { currentItem = item; currentMediaSource = getOptimalMediaSource(item.MediaType, item.MediaSources); mediaElement = playAudio(item, currentMediaSource, startPosition); - mediaControls.show(); self.currentDurationTicks = currentMediaSource.RunTimeTicks; @@ -488,36 +452,50 @@ currentMediaElement = mediaElement; - //display image and title - var imageTags = item.ImageTags || {}; - var html = ''; + if (item.MediaType === "Video") { + + self.updateNowPlayingInfo(item); + } + }; + + self.updateNowPlayingInfo = function (item) { + + if (!item) { + throw new Error('item cannot be null'); + } + + var mediaControls = $("#videoControls"); + + var state = self.getPlayerState(currentMediaElement, item, currentMediaSource); var url = ""; - if (imageTags.Primary) { + if (state.primaryImageTag) { - url = ApiClient.getImageUrl(item.Id, { + url = ApiClient.getImageUrl(state.primaryImageItemId, { type: "Primary", height: 80, - tag: item.ImageTags.Primary + tag: state.primaryImageTag }); } - else if (item.BackdropImageTags && item.BackdropImageTags.length) { + else if (state.backdropImageTag) { - url = ApiClient.getImageUrl(item.Id, { + url = ApiClient.getImageUrl(state.backdropItemId, { type: "Backdrop", height: 80, - tag: item.BackdropImageTags[0] + tag: state.backdropImageTag, + index: 0 }); - } else if (imageTags.Thumb) { - url = ApiClient.getImageUrl(item.Id, { + } else if (state.thumbImageTag) { + + url = ApiClient.getImageUrl(state.thumbImageItemId, { type: "Thumb", height: 80, - tag: item.ImageTags.Thumb + tag: state.thumbImageTag }); - } + else if (item.Type == "TvChannel" || item.Type == "Recording") { url = "css/images/items/detail/tv.png"; } @@ -528,40 +506,19 @@ url = "css/images/items/detail/video.png"; } - var name = item.Name; - var seriesName = ''; + var name = state.itemName; - // Channel number - if (item.Number) { - name = item.Number + ' ' + name; - } - if (item.IndexNumber != null) { - name = item.IndexNumber + " - " + name; - } - if (item.ParentIndexNumber != null) { - name = item.ParentIndexNumber + "." + name; - } - if (item.SeriesName || item.Album || item.ProductionYear) { - seriesName = item.SeriesName || item.Album || item.ProductionYear; - } - if (item.CurrentProgram) { - seriesName = item.CurrentProgram.Name; + var nowPlayingTextElement = $('.nowPlayingText', mediaControls); + + if (state.itemSubName) { + name += '
' + state.itemSubName; + nowPlayingTextElement.addClass('nowPlayingDoubleText'); + } else { + nowPlayingTextElement.removeClass('nowPlayingDoubleText'); } - var href = LibraryBrowser.getHref(item.CurrentProgram || item); - - var nowPlayingText = (name ? name + "\n" : "") + (seriesName || "---"); - if (item.SeriesName || item.Album || item.CurrentProgram) { - nowPlayingText = (seriesName ? seriesName : "") + "\n" + (name || "---"); - } - - // Fix for apostrophes and quotes - var htmlTitle = trimTitle(nowPlayingText).replace(/'/g, ''').replace(/"/g, '"'); - html += "
" + htmlTitle +
-                "
"; - html += "
" + titleHtml(nowPlayingText) + "
"; - - $('.nowPlayingMediaInfo', mediaControls).html(html); + $('.nowPlayingImage', mediaControls).html(''); + nowPlayingTextElement.html(name); }; self.getItemsForPlayback = function (query) { @@ -727,51 +684,65 @@ self.mute = function () { - if (currentMediaElement) { - currentMediaElement.volume = 0; - currentMediaElement.muted = true; - self.volumeSlider.val(0).slider('refresh'); - } + self.setVolume(0); }; - self.unmute = function () { + self.unMute = function () { - if (currentMediaElement) { - var volume = localStorage.getItem("volume") || self.volumeSlider.val(); - currentMediaElement.volume = volume; - currentMediaElement.muted = false; - self.volumeSlider.val(volume).slider('refresh'); - } + self.setVolume(self.getSavedVolume() * 100); }; self.toggleMute = function () { if (currentMediaElement) { - 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'); + + if (currentMediaElement.volume) { + self.mute(); + } else { + self.unMute(); + } } }; self.volumeDown = function () { if (currentMediaElement) { - currentMediaElement.volume = Math.max(currentMediaElement.volume - .02, 0); - localStorage.setItem("volume", currentMediaElement.volume); - self.volumeSlider.val(currentMediaElement.volume).slider('refresh'); + self.setVolume(Math.max(currentMediaElement.volume - .02, 0) * 100); } }; self.volumeUp = function () { if (currentMediaElement) { - currentMediaElement.volume = Math.min(currentMediaElement.volume + .02, 1); - localStorage.setItem("volume", currentMediaElement.volume); - self.volumeSlider.val(currentMediaElement.volume).slider('refresh'); + self.setVolume(Math.min(currentMediaElement.volume + .02, 1) * 100); } }; + // Sets volume using a 0-100 scale + self.setVolume = function (val) { + + if (currentMediaElement) { + + currentMediaElement.volume = val / 100; + + self.onVolumeChanged(currentMediaElement); + + self.saveVolume(); + } + }; + + self.saveVolume = function (val) { + + if (val) { + localStorage.setItem("volume", val); + } + + }; + + self.getSavedVolume = function () { + return localStorage.getItem("volume") || 0.5; + }; + self.shuffle = function (id) { var userId = Dashboard.getCurrentUserId(); @@ -882,7 +853,12 @@ $(elem).off("ended.playnext").on("ended", function () { - $(this).remove(); + $(this).off(); + + if (this.tagName.toLowerCase() != 'audio') { + $(this).remove(); + } + elem.src = ""; currentMediaElement = null; @@ -893,8 +869,6 @@ self.exitFullScreen(); } self.resetEnhancements(); - } else { - $('#nowPlayingBar').hide(); } }; @@ -902,55 +876,150 @@ return currentMediaElement; }; - self.bindPositionSlider = function () { - self.positionSlider.on('slidestart', function (e) { + self.getPlayerState = function (playerElement, item, mediaSource) { - self.isPositionSliderActive = true; + var itemName = ''; + var itemSubName = ''; - }).on('slidestop', onPositionSliderChange); + if (item) { + + var name = item.Name; + var seriesName = ''; + + // Channel number + if (item.Number) { + name = item.Number + ' ' + name; + } + if (item.IndexNumber != null) { + name = item.IndexNumber + " - " + name; + } + if (item.ParentIndexNumber != null) { + name = item.ParentIndexNumber + "." + name; + } + + if (item.CurrentProgram) { + seriesName = item.CurrentProgram.Name; + } + else if (item.SeriesName || item.Album || item.ProductionYear) { + seriesName = item.SeriesName || item.Album || item.ProductionYear; + } + + if (seriesName) { + itemName = item.SeriesName || item.Album || item.CurrentProgram; + itemSubName = name; + } else { + itemName = name; + } + } + + var state = { + itemId: item.Id, + mediaSourceId: mediaSource.Id, + volumeLevel: playerElement.volume * 100, + isMuted: playerElement.volume == 0, + isPaused: playerElement.paused, + runtimeTicks: mediaSource.RunTimeTicks, + positionTicks: self.getCurrentTicks(playerElement), + canSeek: mediaSource.RunTimeTicks && mediaSource.RunTimeTicks > 0, + mediaType: item.MediaType, + itemName: itemName, + itemSubName: itemSubName, + itemType: item.Type + }; + + var imageTags = item.ImageTags || {}; + + if (imageTags.Primary) { + + state.primaryImageItemId = item.Id; + state.primaryImageTag = imageTags.Primary; + } + + if (item.BackdropImageTags && item.BackdropImageTags.length) { + + state.backdropItemId = item.Id; + state.backdropImageTag = item.BackdropImageTags[0]; + } + + if (imageTags.Thumb) { + + state.thumbItemId = item.Id; + state.thumbImageTag = imageTags.Thumb; + } + + return state; }; - self.bindVolumeSlider = function () { - self.volumeSlider.on('slidestop', function () { + self.onPlaybackStart = function (playerElement, item, mediaSource) { - var vol = this.value; + self.updateCanClientSeek(playerElement); - self.updateVolumeButtons(vol); - currentMediaElement.volume = vol; - }); + ApiClient.reportPlaybackStart(Dashboard.getCurrentUserId(), item.Id, mediaSource.Id, true, item.MediaType); + + self.startProgressInterval(item.Id, mediaSource.Id); + + var state = self.getPlayerState(playerElement, item, mediaSource); + + $(self).trigger('playbackstart', [state]); + }; + + self.onVolumeChanged = function (playerElement) { + + self.saveVolume(playerElement.volume); + + var state = self.getPlayerState(playerElement, currentItem, currentMediaSource); + + $(self).trigger('volumechange', [state]); + }; + + self.onPlaybackStopped = function () { + + self.clearPauseStop(); + + var playerElement = this; + + $(playerElement).off('ended.playbackstopped'); + + var endTime = playerElement.currentTime; + + clearProgressInterval(); + + var position = Math.floor(10000000 * endTime) + self.startTimeTicksOffset; + + var item = currentItem; + var mediaSource = currentMediaSource; + + ApiClient.reportPlaybackStopped(Dashboard.getCurrentUserId(), item.Id, mediaSource.Id, position); + + if (item.MediaType == "Video") { + ApiClient.stopActiveEncodings(); + if (self.isFullScreen()) { + self.exitFullScreen(); + } + self.resetEnhancements(); + } + + var state = self.getPlayerState(playerElement, item, mediaSource); + + $(self).trigger('playbackstop', [state]); + }; + + self.onPlaystateChange = function (playerElement) { + + var state = self.getPlayerState(playerElement, currentItem, currentMediaSource); + + $(self).trigger('playstatechange', [state]); }; $(window).on("beforeunload popstate", function () { - var item = currentItem; - var media = currentMediaElement; - // Try to report playback stopped before the browser closes - if (item && media && currentProgressInterval) { + if (currentItem && currentMediaElement && currentProgressInterval) { - var endTime = currentMediaElement.currentTime; - - var position = Math.floor(10000000 * endTime) + self.startTimeTicksOffset; - - ApiClient.reportPlaybackStopped(Dashboard.getCurrentUserId(), currentItem.Id, currentMediaSource.Id, position); + self.onPlaybackStopped.call(currentMediaElement); } }); - $(function () { - initPlayer(); - }); - - function initPlayer() { - self.muteButton = $('.muteButton'); - self.unmuteButton = $('.unmuteButton'); - self.currentTimeElement = $('.currentTime'); - self.volumeSlider = $('.volumeSlider'); - self.positionSlider = $(".positionSlider"); - - self.bindVolumeSlider(); - self.bindPositionSlider(); - } - function replaceQueryString(url, param, value) { var re = new RegExp("([?|&])" + param + "=.*?(&|$)", "i"); if (url.match(re)) @@ -977,15 +1046,30 @@ return testableVideoElement.canPlayType('video/webm').replace(/no/, ''); } - function onPositionSliderChange() { + function getAudioElement() { - self.isPositionSliderActive = false; + var elem = $('.mediaPlayerAudio'); - var newPercent = parseInt(this.value); + if (elem.length) { + return elem; + } - var newPositionTicks = (newPercent / 100) * currentMediaSource.RunTimeTicks; + var html = ''; - self.changeStream(Math.floor(newPositionTicks)); + var requiresControls = $.browser.android || ($.browser.webkit && !$.browser.chrome); + + if (requiresControls) { + html += '
';; + } else { + html += ''; + + $(document.body).append(html); + + return $('.mediaPlayerAudio'); } function playAudio(item, mediaSource, startPositionTicks) { @@ -1003,18 +1087,10 @@ audioCodec: 'mp3' })); - var aacUrl = ApiClient.getUrl('Audio/' + item.Id + '/stream.aac', $.extend({}, baseParams, { - audioCodec: 'aac' - })); - - var webmUrl = ApiClient.getUrl('Audio/' + item.Id + '/stream.webm', $.extend({}, baseParams, { - audioCodec: 'Vorbis' - })); - var mediaStreams = mediaSource.MediaStreams; var isStatic = false; - var seekParam = isStatic && startPositionTicks ? '#t=' + (startPositionTicks / 10000000) : ''; + var seekParam = startPositionTicks ? '#t=' + (startPositionTicks / 10000000) : ''; for (var i = 0, length = mediaStreams.length; i < length; i++) { @@ -1024,11 +1100,7 @@ var container = (mediaSource.Container || '').toLowerCase(); // Stream statically when possible - if (container == 'aac' && stream.BitRate <= 256000) { - aacUrl += "&static=true" + seekParam; - isStatic = true; - } - else if (container == 'mp3' && stream.BitRate <= 256000) { + if (container == 'mp3' && stream.BitRate <= 256000) { mp3Url += "&static=true" + seekParam; isStatic = true; } @@ -1038,91 +1110,39 @@ self.startTimeTicksOffset = isStatic ? 0 : startPositionTicks; - var html = ''; + var initialVolume = self.getSavedVolume(); - var requiresControls = $.browser.android || ($.browser.webkit && !$.browser.chrome); + return getAudioElement().each(function () { - // Can't autoplay in these browsers so we need to use the full controls - if (requiresControls) { - html += '