mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
use server to build initial stream url's
This commit is contained in:
parent
c64474f046
commit
1fc390dc50
3 changed files with 375 additions and 295 deletions
|
@ -428,14 +428,12 @@
|
||||||
|
|
||||||
if (!$(this).hasClass('selectedMediaPopupOption')) {
|
if (!$(this).hasClass('selectedMediaPopupOption')) {
|
||||||
|
|
||||||
var maxWidth = parseInt(this.getAttribute('data-maxwidth'));
|
|
||||||
var bitrate = parseInt(this.getAttribute('data-bitrate'));
|
var bitrate = parseInt(this.getAttribute('data-bitrate'));
|
||||||
|
|
||||||
AppSettings.maxStreamingBitrate(bitrate);
|
AppSettings.maxStreamingBitrate(bitrate);
|
||||||
|
|
||||||
self.changeStream(self.getCurrentTicks(), {
|
self.changeStream(self.getCurrentTicks(), {
|
||||||
|
|
||||||
MaxWidth: maxWidth,
|
|
||||||
Bitrate: bitrate
|
Bitrate: bitrate
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -796,7 +794,12 @@
|
||||||
var currentSrc = self.getCurrentSrc(self.currentMediaElement).toLowerCase();
|
var currentSrc = self.getCurrentSrc(self.currentMediaElement).toLowerCase();
|
||||||
var isStatic = currentSrc.indexOf('static=true') != -1;
|
var isStatic = currentSrc.indexOf('static=true') != -1;
|
||||||
|
|
||||||
var options = getVideoQualityOptions(self.currentMediaSource.MediaStreams);
|
var videoStream = self.currentMediaSource.MediaStreams.filter(function (stream) {
|
||||||
|
return stream.Type == "Video";
|
||||||
|
})[0];
|
||||||
|
var videoWidth = videoStream ? videoStream.Width : null;
|
||||||
|
|
||||||
|
var options = self.getVideoQualityOptions(videoWidth);
|
||||||
|
|
||||||
if (isStatic) {
|
if (isStatic) {
|
||||||
options[0].name = "Direct";
|
options[0].name = "Direct";
|
||||||
|
@ -819,7 +822,7 @@
|
||||||
cssClass += ' selectedMediaPopupOption';
|
cssClass += ' selectedMediaPopupOption';
|
||||||
}
|
}
|
||||||
|
|
||||||
var optionHtml = '<li><a data-maxwidth="' + option.maxWidth + '" data-bitrate="' + option.bitrate + '" class="' + cssClass + '" href="#">';
|
var optionHtml = '<li><a data-bitrate="' + option.bitrate + '" class="' + cssClass + '" href="#">';
|
||||||
|
|
||||||
optionHtml += '<p style="margin:0;">';
|
optionHtml += '<p style="margin:0;">';
|
||||||
|
|
||||||
|
@ -845,85 +848,6 @@
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVideoQualityOptions(mediaStreams) {
|
|
||||||
|
|
||||||
var videoStream = mediaStreams.filter(function (stream) {
|
|
||||||
return stream.Type == "Video";
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
var bitrateSetting = AppSettings.maxStreamingBitrate();
|
|
||||||
|
|
||||||
var maxAllowedWidth = self.getMaxPlayableWidth();
|
|
||||||
|
|
||||||
var options = [];
|
|
||||||
|
|
||||||
// We have media info
|
|
||||||
if (videoStream && videoStream.Width) {
|
|
||||||
|
|
||||||
maxAllowedWidth = videoStream.Width;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some 1080- videos are reported as 1912?
|
|
||||||
if (maxAllowedWidth >= 1900) {
|
|
||||||
options.push({ name: '1080p - 30Mbps', maxWidth: 1920, bitrate: 30000000 });
|
|
||||||
options.push({ name: '1080p - 25Mbps', maxWidth: 1920, bitrate: 25000000 });
|
|
||||||
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 >= 1260) {
|
|
||||||
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 >= 460) {
|
|
||||||
options.push({ name: '480p - 4Mbps', maxWidth: 720, bitrate: 4000000 });
|
|
||||||
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 >= 1260) {
|
|
||||||
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 });
|
|
||||||
|
|
||||||
// The extra 1 is because they're keyed off the bitrate value
|
|
||||||
options.push({ name: '720p - 1Mbps', maxWidth: 1280, bitrate: 1000001 });
|
|
||||||
}
|
|
||||||
|
|
||||||
options.push({ name: '480p - 1.0Mbps', maxWidth: 720, bitrate: 1000000 });
|
|
||||||
options.push({ name: '480p - 720kbps', maxWidth: 720, bitrate: 720000 });
|
|
||||||
options.push({ name: '480p - 420kbps', 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 bindEventsForPlayback() {
|
function bindEventsForPlayback() {
|
||||||
|
|
||||||
var hideElementsOnIdle = !$.browser.mobile;
|
var hideElementsOnIdle = !$.browser.mobile;
|
||||||
|
@ -1007,101 +931,71 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.playVideo = function (playbackInfo, item, mediaSource, startPosition) {
|
self.playVideo = function (deviceProfile, playbackInfo, item, mediaSource, startPosition) {
|
||||||
|
|
||||||
|
var videoUrl;
|
||||||
|
var contentType;
|
||||||
|
|
||||||
var mediaStreams = mediaSource.MediaStreams || [];
|
var mediaStreams = mediaSource.MediaStreams || [];
|
||||||
|
|
||||||
var subtitleStreams = mediaStreams.filter(function (s) {
|
var subtitleStreams = mediaStreams.filter(function (s) {
|
||||||
return s.Type == 'Subtitle';
|
return s.Type == 'Subtitle';
|
||||||
});
|
});
|
||||||
|
|
||||||
var selectedSubtitleStream = subtitleStreams.filter(function (s) {
|
if (mediaSource.enableDirectPlay) {
|
||||||
return s.Index == mediaSource.DefaultSubtitleStreamIndex;
|
videoUrl = mediaSource.Path;
|
||||||
|
self.startTimeTicksOffset = 0;
|
||||||
})[0];
|
contentType = 'video/' + mediaSource.Container;
|
||||||
|
|
||||||
var baseParams = {
|
|
||||||
audioChannels: 2,
|
|
||||||
StartTimeTicks: startPosition,
|
|
||||||
AudioStreamIndex: mediaSource.DefaultAudioStreamIndex,
|
|
||||||
deviceId: ApiClient.deviceId(),
|
|
||||||
Static: false,
|
|
||||||
mediaSourceId: mediaSource.Id,
|
|
||||||
api_key: ApiClient.accessToken(),
|
|
||||||
StreamId: playbackInfo.StreamId
|
|
||||||
};
|
|
||||||
|
|
||||||
if (selectedSubtitleStream && (!self.supportsSubtitleStreamExternally(selectedSubtitleStream) || !self.supportsTextTracks())) {
|
|
||||||
baseParams.SubtitleStreamIndex = mediaSource.DefaultSubtitleStreamIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mp4Quality = getVideoQualityOptions(mediaStreams).filter(function (opt) {
|
|
||||||
return opt.selected;
|
|
||||||
})[0];
|
|
||||||
mp4Quality = $.extend(mp4Quality, self.getFinalVideoParams(mediaSource, mp4Quality.maxWidth, mp4Quality.bitrate, baseParams.AudioStreamIndex, baseParams.SubtitleStreamIndex, '.mp4'));
|
|
||||||
|
|
||||||
var webmQuality = getVideoQualityOptions(mediaStreams).filter(function (opt) {
|
|
||||||
return opt.selected;
|
|
||||||
})[0];
|
|
||||||
webmQuality = $.extend(webmQuality, self.getFinalVideoParams(mediaSource, webmQuality.maxWidth, webmQuality.bitrate, baseParams.AudioStreamIndex, baseParams.SubtitleStreamIndex, '.webm'));
|
|
||||||
|
|
||||||
var m3U8Quality = getVideoQualityOptions(mediaStreams).filter(function (opt) {
|
|
||||||
return opt.selected;
|
|
||||||
})[0];
|
|
||||||
m3U8Quality = $.extend(m3U8Quality, self.getFinalVideoParams(mediaSource, mp4Quality.maxWidth, mp4Quality.bitrate, baseParams.AudioStreamIndex, baseParams.SubtitleStreamIndex, '.mp4'));
|
|
||||||
|
|
||||||
var isStatic = mp4Quality.isStatic;
|
|
||||||
|
|
||||||
self.startTimeTicksOffset = isStatic ? 0 : startPosition || 0;
|
|
||||||
|
|
||||||
var startPositionInSeekParam = startPosition ? (startPosition / 10000000) : 0;
|
|
||||||
var seekParam = startPositionInSeekParam ? '#t=' + startPositionInSeekParam : '';
|
|
||||||
|
|
||||||
var mp4VideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.mp4', $.extend({}, baseParams, {
|
|
||||||
Static: isStatic,
|
|
||||||
maxWidth: mp4Quality.maxWidth,
|
|
||||||
videoBitrate: mp4Quality.videoBitrate,
|
|
||||||
audioBitrate: mp4Quality.audioBitrate,
|
|
||||||
VideoCodec: mp4Quality.videoCodec,
|
|
||||||
AudioCodec: mp4Quality.audioCodec,
|
|
||||||
profile: 'high',
|
|
||||||
//EnableAutoStreamCopy: false,
|
|
||||||
level: '41'
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (isStatic && mediaSource.Protocol == 'Http' && !mediaSource.RequiredHttpHeaders.length) {
|
|
||||||
mp4VideoUrl = mediaSource.Path;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isStatic) {
|
|
||||||
mp4VideoUrl += seekParam;
|
|
||||||
} else {
|
} else {
|
||||||
mp4VideoUrl += "&StreamId=" + new Date().getTime();
|
|
||||||
|
var selectedSubtitleStream = subtitleStreams.filter(function (s) {
|
||||||
|
return s.Index == mediaSource.DefaultSubtitleStreamIndex;
|
||||||
|
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
var transcodingParams = {
|
||||||
|
audioChannels: 2,
|
||||||
|
StartTimeTicks: startPosition,
|
||||||
|
AudioStreamIndex: mediaSource.DefaultAudioStreamIndex,
|
||||||
|
deviceId: ApiClient.deviceId(),
|
||||||
|
mediaSourceId: mediaSource.Id,
|
||||||
|
api_key: ApiClient.accessToken(),
|
||||||
|
StreamId: playbackInfo.StreamId,
|
||||||
|
ClientTime: new Date().getTime()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (selectedSubtitleStream && (!self.supportsSubtitleStreamExternally(selectedSubtitleStream) || !self.supportsTextTracks())) {
|
||||||
|
transcodingParams.SubtitleStreamIndex = mediaSource.DefaultSubtitleStreamIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.startTimeTicksOffset = mediaSource.SupportsDirectStream ? 0 : startPosition || 0;
|
||||||
|
var startPositionInSeekParam = startPosition ? (startPosition / 10000000) : 0;
|
||||||
|
var seekParam = startPositionInSeekParam ? '#t=' + startPositionInSeekParam : '';
|
||||||
|
|
||||||
|
if (mediaSource.SupportsDirectStream) {
|
||||||
|
|
||||||
|
videoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.' + mediaSource.Container, {
|
||||||
|
Static: true,
|
||||||
|
mediaSourceId: mediaSource.Id,
|
||||||
|
api_key: ApiClient.accessToken()
|
||||||
|
});
|
||||||
|
videoUrl += seekParam;
|
||||||
|
contentType = 'video/' + mediaSource.Container;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
videoUrl = ApiClient.getUrl(mediaSource.TranscodingUrl);
|
||||||
|
|
||||||
|
if (mediaSource.TranscodingSubProtocol == 'hls') {
|
||||||
|
|
||||||
|
videoUrl += seekParam;
|
||||||
|
contentType = 'application/x-mpegURL';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
contentType = 'video/' + mediaSource.TranscodingContainer;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var webmVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/stream.webm', $.extend({}, baseParams, {
|
|
||||||
VideoCodec: 'vpx',
|
|
||||||
AudioCodec: 'Vorbis',
|
|
||||||
maxWidth: webmQuality.maxWidth,
|
|
||||||
videoBitrate: webmQuality.videoBitrate,
|
|
||||||
audioBitrate: webmQuality.audioBitrate,
|
|
||||||
EnableAutoStreamCopy: false,
|
|
||||||
StreamId: new Date().getTime()
|
|
||||||
}));
|
|
||||||
|
|
||||||
var hlsVideoUrl = ApiClient.getUrl('Videos/' + item.Id + '/master.m3u8', $.extend({}, baseParams, {
|
|
||||||
maxWidth: m3U8Quality.maxWidth,
|
|
||||||
videoBitrate: m3U8Quality.videoBitrate,
|
|
||||||
audioBitrate: m3U8Quality.audioBitrate,
|
|
||||||
VideoCodec: m3U8Quality.videoCodec,
|
|
||||||
AudioCodec: m3U8Quality.audioCodec,
|
|
||||||
profile: 'high',
|
|
||||||
level: '41',
|
|
||||||
StartTimeTicks: 0,
|
|
||||||
StreamId: new Date().getTime()
|
|
||||||
|
|
||||||
})) + seekParam;
|
|
||||||
|
|
||||||
//======================================================================================>
|
//======================================================================================>
|
||||||
|
|
||||||
// Create video player
|
// Create video player
|
||||||
|
@ -1118,27 +1012,7 @@
|
||||||
html += '<video class="itemVideo" id="itemVideo" preload="metadata" autoplay>';
|
html += '<video class="itemVideo" id="itemVideo" preload="metadata" autoplay>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isStatic) {
|
html += '<source type="' + contentType + '" src="' + videoUrl + '" />';
|
||||||
// HLS must be at the top for safari
|
|
||||||
html += '<source type="application/x-mpegURL" src="' + hlsVideoUrl + '" />';
|
|
||||||
}
|
|
||||||
|
|
||||||
var mp4BeforeWebm = self.getVideoTranscodingExtension() != '.webm';
|
|
||||||
|
|
||||||
if (mp4BeforeWebm) {
|
|
||||||
html += '<source type="video/mp4" src="' + mp4VideoUrl + '" />';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Have to put webm ahead of mp4 because it will play in fast forward in chrome
|
|
||||||
// And firefox doesn't like fragmented mp4
|
|
||||||
if (!isStatic) {
|
|
||||||
|
|
||||||
html += '<source type="video/webm" src="' + webmVideoUrl + '" />';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mp4BeforeWebm) {
|
|
||||||
html += '<source type="video/mp4" src="' + mp4VideoUrl + '" />';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.supportsTextTracks()) {
|
if (self.supportsTextTracks()) {
|
||||||
var textStreams = subtitleStreams.filter(function (s) {
|
var textStreams = subtitleStreams.filter(function (s) {
|
||||||
|
|
|
@ -37,6 +37,245 @@
|
||||||
return targets;
|
return targets;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var supportsAac = document.createElement('audio').canPlayType('audio/aac').replace(/no/, '');
|
||||||
|
|
||||||
|
self.getVideoQualityOptions = function (videoWidth) {
|
||||||
|
|
||||||
|
var bitrateSetting = AppSettings.maxStreamingBitrate();
|
||||||
|
|
||||||
|
var maxAllowedWidth = videoWidth || 4096;
|
||||||
|
|
||||||
|
var options = [];
|
||||||
|
|
||||||
|
// Some 1080- videos are reported as 1912?
|
||||||
|
if (maxAllowedWidth >= 1900) {
|
||||||
|
options.push({ name: '1080p - 30Mbps', maxWidth: 1920, bitrate: 30000000 });
|
||||||
|
options.push({ name: '1080p - 25Mbps', maxWidth: 1920, bitrate: 25000000 });
|
||||||
|
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 >= 1260) {
|
||||||
|
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 >= 460) {
|
||||||
|
options.push({ name: '480p - 4Mbps', maxWidth: 720, bitrate: 4000000 });
|
||||||
|
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 >= 1260) {
|
||||||
|
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 });
|
||||||
|
|
||||||
|
// The extra 1 is because they're keyed off the bitrate value
|
||||||
|
options.push({ name: '720p - 1Mbps', maxWidth: 1280, bitrate: 1000001 });
|
||||||
|
}
|
||||||
|
|
||||||
|
options.push({ name: '480p - 1.0Mbps', maxWidth: 720, bitrate: 1000000 });
|
||||||
|
options.push({ name: '480p - 720kbps', maxWidth: 720, bitrate: 720000 });
|
||||||
|
options.push({ name: '480p - 420kbps', 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.getDeviceProfile = function () {
|
||||||
|
|
||||||
|
var qualityOption = self.getVideoQualityOptions().filter(function (q) {
|
||||||
|
return q.selected;
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
var bitrateSetting = AppSettings.maxStreamingBitrate();
|
||||||
|
|
||||||
|
var profile = {};
|
||||||
|
|
||||||
|
profile.MaxStreamingBitrate = bitrateSetting;
|
||||||
|
profile.MaxStaticBitrate = 40000000;
|
||||||
|
profile.MusicStreamingTranscodingBitrate = 128000;
|
||||||
|
|
||||||
|
profile.DirectPlayProfiles = [];
|
||||||
|
profile.DirectPlayProfiles.push({
|
||||||
|
Container: 'mp4',
|
||||||
|
Type: 'Video',
|
||||||
|
VideoCodec: 'h264',
|
||||||
|
AudioCodec: 'aac,mp3'
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($.browser.chrome) {
|
||||||
|
profile.DirectPlayProfiles.push({
|
||||||
|
Container: 'mkv,m4v',
|
||||||
|
Type: 'Video',
|
||||||
|
VideoCodec: 'h264',
|
||||||
|
AudioCodec: 'aac,mp3'
|
||||||
|
});
|
||||||
|
profile.DirectPlayProfiles.push({
|
||||||
|
Container: 'flv',
|
||||||
|
Type: 'Video'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.DirectPlayProfiles.push({
|
||||||
|
Container: 'mp3',
|
||||||
|
Type: 'Audio'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (supportsAac) {
|
||||||
|
profile.DirectPlayProfiles.push({
|
||||||
|
Container: 'aac',
|
||||||
|
Type: 'Audio'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.TranscodingProfiles = [];
|
||||||
|
profile.TranscodingProfiles.push({
|
||||||
|
Container: 'mp3',
|
||||||
|
Type: 'Audio',
|
||||||
|
AudioCodec: 'mp3',
|
||||||
|
Context: 'Streaming',
|
||||||
|
Protocol: 'http'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (self.canPlayHls()) {
|
||||||
|
profile.TranscodingProfiles.push({
|
||||||
|
Container: 'ts',
|
||||||
|
Type: 'Video',
|
||||||
|
AudioCodec: 'aac',
|
||||||
|
VideoCodec: 'h264',
|
||||||
|
Context: 'Streaming',
|
||||||
|
Protocol: 'hls'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.canPlayWebm()) {
|
||||||
|
|
||||||
|
profile.TranscodingProfiles.push({
|
||||||
|
Container: 'webm',
|
||||||
|
Type: 'Video',
|
||||||
|
AudioCodec: 'vorbis',
|
||||||
|
VideoCodec: 'vpx',
|
||||||
|
Context: 'Streaming',
|
||||||
|
Protocol: 'http'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.TranscodingProfiles.push({
|
||||||
|
Container: 'mp4',
|
||||||
|
Type: 'Video',
|
||||||
|
AudioCodec: 'aac',
|
||||||
|
VideoCodec: 'h264',
|
||||||
|
Context: 'Streaming',
|
||||||
|
Protocol: 'http'
|
||||||
|
});
|
||||||
|
|
||||||
|
profile.ContainerProfiles = [];
|
||||||
|
|
||||||
|
var audioConditions = [];
|
||||||
|
if ($.browser.msie) {
|
||||||
|
audioConditions.push({
|
||||||
|
Condition: 'LessThanEqual',
|
||||||
|
Property: 'AudioChannels',
|
||||||
|
Value: '2'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.CodecProfiles = [];
|
||||||
|
profile.CodecProfiles.push({
|
||||||
|
Type: 'Audio',
|
||||||
|
Conditions: audioConditions
|
||||||
|
});
|
||||||
|
|
||||||
|
profile.CodecProfiles.push({
|
||||||
|
Type: 'VideoAudio',
|
||||||
|
Conditions: audioConditions
|
||||||
|
});
|
||||||
|
|
||||||
|
profile.CodecProfiles.push({
|
||||||
|
Type: 'Video',
|
||||||
|
Codec: 'h264',
|
||||||
|
Conditions: [
|
||||||
|
{
|
||||||
|
Condition: 'Equals',
|
||||||
|
Property: 'IsCabac',
|
||||||
|
Value: 'true'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Condition: 'NotEquals',
|
||||||
|
Property: 'IsAnamorphic',
|
||||||
|
Value: 'true'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Condition: 'EqualsAny',
|
||||||
|
Property: 'VideoProfile',
|
||||||
|
Value: 'high|main|baseline|constrained baseline'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Condition: 'LessThanEqual',
|
||||||
|
Property: 'VideoLevel',
|
||||||
|
Value: '41'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Condition: 'LessThanEqual',
|
||||||
|
Property: 'Width',
|
||||||
|
Value: qualityOption.maxWidth
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
profile.CodecProfiles.push({
|
||||||
|
Type: 'Video',
|
||||||
|
Codec: 'vpx',
|
||||||
|
Conditions: [
|
||||||
|
{
|
||||||
|
Condition: 'NotEquals',
|
||||||
|
Property: 'IsAnamorphic',
|
||||||
|
Value: 'true'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Condition: 'LessThanEqual',
|
||||||
|
Property: 'Width',
|
||||||
|
Value: qualityOption.maxWidth
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Subtitle profiles
|
||||||
|
// External vtt or burn in
|
||||||
|
profile.SubtitleProfiles = [];
|
||||||
|
if (self.supportsTextTracks()) {
|
||||||
|
profile.SubtitleProfiles.push({
|
||||||
|
Format: 'vtt',
|
||||||
|
Method: 'External'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
};
|
||||||
|
|
||||||
self.updateCanClientSeek = function (elem) {
|
self.updateCanClientSeek = function (elem) {
|
||||||
|
|
||||||
var duration = elem.duration;
|
var duration = elem.duration;
|
||||||
|
@ -89,17 +328,27 @@
|
||||||
return currentSrc.substring(currentSrc.lastIndexOf('.'));
|
return currentSrc.substring(currentSrc.lastIndexOf('.'));
|
||||||
};
|
};
|
||||||
|
|
||||||
self.getVideoTranscodingExtension = function (currentSrc) {
|
self.canPlayHls = function () {
|
||||||
|
|
||||||
if (currentSrc) {
|
|
||||||
return self.getCurrentMediaExtension(currentSrc);
|
|
||||||
}
|
|
||||||
|
|
||||||
var media = testableVideoElement;
|
var media = testableVideoElement;
|
||||||
|
|
||||||
// safari
|
// safari
|
||||||
if (media.canPlayType('application/x-mpegURL').replace(/no/, '') ||
|
if (media.canPlayType('application/x-mpegURL').replace(/no/, '') ||
|
||||||
media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')) {
|
media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.getVideoTranscodingExtension = function (currentSrc) {
|
||||||
|
|
||||||
|
if (currentSrc) {
|
||||||
|
return self.getCurrentMediaExtension(currentSrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// safari
|
||||||
|
if (self.canPlayHls()) {
|
||||||
return '.m3u8';
|
return '.m3u8';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +400,6 @@
|
||||||
currentSrc = replaceQueryString(currentSrc, 'SubtitleStreamIndex', (params.SubtitleStreamIndex == -1 ? '' : params.SubtitleStreamIndex));
|
currentSrc = replaceQueryString(currentSrc, 'SubtitleStreamIndex', (params.SubtitleStreamIndex == -1 ? '' : params.SubtitleStreamIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxWidth = params.MaxWidth || getParameterByName('MaxWidth', currentSrc);
|
|
||||||
var audioStreamIndex = params.AudioStreamIndex == null ? getParameterByName('AudioStreamIndex', currentSrc) : params.AudioStreamIndex;
|
var audioStreamIndex = params.AudioStreamIndex == null ? getParameterByName('AudioStreamIndex', currentSrc) : params.AudioStreamIndex;
|
||||||
if (typeof (audioStreamIndex) == 'string') {
|
if (typeof (audioStreamIndex) == 'string') {
|
||||||
audioStreamIndex = parseInt(audioStreamIndex);
|
audioStreamIndex = parseInt(audioStreamIndex);
|
||||||
|
@ -161,9 +409,8 @@
|
||||||
var audioBitrate = parseInt(getParameterByName('AudioBitrate', currentSrc) || '0');
|
var audioBitrate = parseInt(getParameterByName('AudioBitrate', currentSrc) || '0');
|
||||||
var bitrate = params.Bitrate || (videoBitrate + audioBitrate);
|
var bitrate = params.Bitrate || (videoBitrate + audioBitrate);
|
||||||
|
|
||||||
var finalParams = self.getFinalVideoParams(self.currentMediaSource, maxWidth, bitrate, audioStreamIndex, subtitleStreamIndex, transcodingExtension);
|
var finalParams = self.getFinalVideoParams(self.currentMediaSource, bitrate, audioStreamIndex, subtitleStreamIndex, transcodingExtension);
|
||||||
|
|
||||||
currentSrc = replaceQueryString(currentSrc, 'MaxWidth', finalParams.maxWidth);
|
|
||||||
currentSrc = replaceQueryString(currentSrc, 'VideoBitrate', finalParams.videoBitrate);
|
currentSrc = replaceQueryString(currentSrc, 'VideoBitrate', finalParams.videoBitrate);
|
||||||
|
|
||||||
currentSrc = replaceQueryString(currentSrc, 'VideoCodec', finalParams.videoCodec);
|
currentSrc = replaceQueryString(currentSrc, 'VideoCodec', finalParams.videoCodec);
|
||||||
|
@ -274,7 +521,7 @@
|
||||||
return supportsTextTracks;
|
return supportsTextTracks;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.getVideoDirectPlayMethod = function (mediaSource, videoStream, audioStream, subtitleStream, maxWidth, bitrate) {
|
self.getVideoDirectPlayMethod = function (mediaSource, videoStream, audioStream, subtitleStream, bitrate) {
|
||||||
|
|
||||||
if (!mediaSource) {
|
if (!mediaSource) {
|
||||||
throw new Error('Null mediaSource');
|
throw new Error('Null mediaSource');
|
||||||
|
@ -322,11 +569,6 @@
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoStream.Width > maxWidth) {
|
|
||||||
console.log('Transcoding because resolution is too high');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (videoStream && videoStream.IsAnamorphic) {
|
if (videoStream && videoStream.IsAnamorphic) {
|
||||||
console.log('Transcoding because video is anamorphic');
|
console.log('Transcoding because video is anamorphic');
|
||||||
return null;
|
return null;
|
||||||
|
@ -372,7 +614,7 @@
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.getFinalVideoParams = function (mediaSource, maxWidth, bitrate, audioStreamIndex, subtitleStreamIndex, transcodingExtension) {
|
self.getFinalVideoParams = function (mediaSource, bitrate, audioStreamIndex, subtitleStreamIndex, transcodingExtension) {
|
||||||
|
|
||||||
var mediaStreams = mediaSource.MediaStreams;
|
var mediaStreams = mediaSource.MediaStreams;
|
||||||
|
|
||||||
|
@ -388,7 +630,7 @@
|
||||||
return stream.Index === subtitleStreamIndex && stream.Type == 'Subtitle';
|
return stream.Index === subtitleStreamIndex && stream.Type == 'Subtitle';
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
var directPlayMethod = self.getVideoDirectPlayMethod(mediaSource, videoStream, audioStream, subtitleStream, maxWidth, bitrate);
|
var directPlayMethod = self.getVideoDirectPlayMethod(mediaSource, videoStream, audioStream, subtitleStream, bitrate);
|
||||||
|
|
||||||
var audioBitrate = bitrate >= 700000 ? 192000 : 64000;
|
var audioBitrate = bitrate >= 700000 ? 192000 : 64000;
|
||||||
|
|
||||||
|
@ -396,7 +638,6 @@
|
||||||
|
|
||||||
var params = {
|
var params = {
|
||||||
isStatic: directPlayMethod != null,
|
isStatic: directPlayMethod != null,
|
||||||
maxWidth: maxWidth,
|
|
||||||
audioCodec: transcodingExtension == '.webm' ? 'vorbis' : 'aac',
|
audioCodec: transcodingExtension == '.webm' ? 'vorbis' : 'aac',
|
||||||
videoCodec: transcodingExtension == '.webm' ? 'vpx' : 'h264',
|
videoCodec: transcodingExtension == '.webm' ? 'vpx' : 'h264',
|
||||||
audioBitrate: audioBitrate,
|
audioBitrate: audioBitrate,
|
||||||
|
@ -504,11 +745,6 @@
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.getMaxPlayableWidth = function () {
|
|
||||||
|
|
||||||
return Math.max(screen.height, screen.width);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.playWithIntros = function (items, options, user) {
|
self.playWithIntros = function (items, options, user) {
|
||||||
|
|
||||||
var firstItem = items[0];
|
var firstItem = items[0];
|
||||||
|
@ -534,38 +770,18 @@
|
||||||
|
|
||||||
function getOptimalMediaSource(mediaType, versions) {
|
function getOptimalMediaSource(mediaType, versions) {
|
||||||
|
|
||||||
var optimalVersion;
|
var optimalVersion = versions.filter(function (v) {
|
||||||
var bitrateSetting = AppSettings.maxStreamingBitrate();
|
|
||||||
|
|
||||||
if (mediaType == 'Video') {
|
v.enableDirectPlay = v.SupportsDirectPlay && v.Protocol == 'Http' && !v.RequiredHttpHeaders.length;
|
||||||
|
|
||||||
var maxAllowedWidth = self.getMaxPlayableWidth();
|
return v.enableDirectPlay;
|
||||||
|
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
if (!optimalVersion) {
|
||||||
optimalVersion = versions.filter(function (v) {
|
optimalVersion = versions.filter(function (v) {
|
||||||
|
|
||||||
var videoStream = v.MediaStreams.filter(function (s) {
|
return v.SupportsDirectStream;
|
||||||
return s.Type == 'Video';
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
var audioStream = v.MediaStreams.filter(function (s) {
|
|
||||||
return s.Type == 'Audio';
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
var directPlayMethod = self.getVideoDirectPlayMethod(v, videoStream, audioStream, null, maxAllowedWidth, bitrateSetting);
|
|
||||||
|
|
||||||
if (directPlayMethod == 'DirectPlay') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return v.SupportsDirectStream && directPlayMethod == 'DirectStream';
|
|
||||||
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
optimalVersion = versions.filter(function (v) {
|
|
||||||
|
|
||||||
return v.SupportsDirectStream && canPlayAudioMediaSourceDirect(v);
|
|
||||||
|
|
||||||
})[0];
|
})[0];
|
||||||
}
|
}
|
||||||
|
@ -595,11 +811,22 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaSource;
|
var mediaSource;
|
||||||
|
var deviceProfile = self.getDeviceProfile();
|
||||||
|
|
||||||
ApiClient.getJSON(ApiClient.getUrl('Items/' + item.Id + '/PlaybackInfo', {
|
ApiClient.ajax({
|
||||||
userId: Dashboard.getCurrentUserId()
|
|
||||||
|
|
||||||
})).done(function (result) {
|
url: ApiClient.getUrl('Items/' + item.Id + '/PlaybackInfo', {
|
||||||
|
userId: Dashboard.getCurrentUserId()
|
||||||
|
|
||||||
|
}),
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
DeviceProfile: deviceProfile
|
||||||
|
}),
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json"
|
||||||
|
|
||||||
|
}).done(function (result) {
|
||||||
|
|
||||||
if (validatePlaybackInfoResult(result)) {
|
if (validatePlaybackInfoResult(result)) {
|
||||||
|
|
||||||
|
@ -612,7 +839,7 @@
|
||||||
|
|
||||||
if (item.MediaType === "Video") {
|
if (item.MediaType === "Video") {
|
||||||
|
|
||||||
self.currentMediaElement = self.playVideo(result, item, self.currentMediaSource, startPosition);
|
self.currentMediaElement = self.playVideo(deviceProfile, result, item, self.currentMediaSource, startPosition);
|
||||||
self.currentDurationTicks = self.currentMediaSource.RunTimeTicks;
|
self.currentDurationTicks = self.currentMediaSource.RunTimeTicks;
|
||||||
|
|
||||||
self.updateNowPlayingInfo(item);
|
self.updateNowPlayingInfo(item);
|
||||||
|
@ -1380,63 +1607,36 @@
|
||||||
return $('.mediaPlayerAudio');
|
return $('.mediaPlayerAudio');
|
||||||
}
|
}
|
||||||
|
|
||||||
function canPlayAudioMediaSourceDirect(mediaSource) {
|
|
||||||
|
|
||||||
var sourceContainer = (mediaSource.Container || '').toLowerCase();
|
|
||||||
|
|
||||||
if (sourceContainer == 'mp3' ||
|
|
||||||
(sourceContainer == 'aac' && supportsAac)) {
|
|
||||||
|
|
||||||
for (var i = 0, length = mediaSource.MediaStreams.length; i < length; i++) {
|
|
||||||
|
|
||||||
var stream = mediaSource.MediaStreams[i];
|
|
||||||
|
|
||||||
if (stream.Type == "Audio") {
|
|
||||||
|
|
||||||
// Stream statically when possible
|
|
||||||
if (stream.BitRate <= 320000) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportsAac = document.createElement('audio').canPlayType('audio/aac').replace(/no/, '');
|
|
||||||
|
|
||||||
function playAudio(playbackInfo, item, mediaSource, startPositionTicks) {
|
function playAudio(playbackInfo, item, mediaSource, startPositionTicks) {
|
||||||
|
|
||||||
startPositionTicks = startPositionTicks || 0;
|
var audioUrl;
|
||||||
|
if (mediaSource.enableDirectPlay) {
|
||||||
|
|
||||||
var baseParams = {
|
audioUrl = mediaSource.Path;
|
||||||
audioChannels: 2,
|
self.startTimeTicksOffset = 0;
|
||||||
audioBitrate: 128000,
|
|
||||||
StartTimeTicks: startPositionTicks,
|
|
||||||
mediaSourceId: mediaSource.Id,
|
|
||||||
deviceId: ApiClient.deviceId(),
|
|
||||||
api_key: ApiClient.accessToken(),
|
|
||||||
StreamId: playbackInfo.StreamId
|
|
||||||
};
|
|
||||||
|
|
||||||
var sourceContainer = (mediaSource.Container || '').toLowerCase();
|
|
||||||
var isStatic = canPlayAudioMediaSourceDirect(mediaSource);
|
|
||||||
|
|
||||||
var outputContainer = isStatic ? sourceContainer : 'mp3';
|
|
||||||
var audioUrl = ApiClient.getUrl('Audio/' + item.Id + '/stream.' + outputContainer, $.extend({}, baseParams, {
|
|
||||||
audioCodec: outputContainer
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (isStatic) {
|
|
||||||
var seekParam = startPositionTicks ? '#t=' + (startPositionTicks / 10000000) : '';
|
|
||||||
audioUrl += "&static=true" + seekParam;
|
|
||||||
} else {
|
} else {
|
||||||
audioUrl += "&ClientTime=" + new Date().getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.startTimeTicksOffset = isStatic ? 0 : startPositionTicks;
|
var isDirectStream = mediaSource.SupportsDirectStream;
|
||||||
|
startPositionTicks = startPositionTicks || 0;
|
||||||
|
|
||||||
|
if (isDirectStream) {
|
||||||
|
|
||||||
|
var outputContainer = (mediaSource.Container || '').toLowerCase();
|
||||||
|
|
||||||
|
var seekParam = startPositionTicks ? '#t=' + (startPositionTicks / 10000000) : '';
|
||||||
|
audioUrl = ApiClient.getUrl('Audio/' + item.Id + '/stream.' + outputContainer, {
|
||||||
|
mediaSourceId: mediaSource.Id,
|
||||||
|
deviceId: ApiClient.deviceId(),
|
||||||
|
api_key: ApiClient.accessToken()
|
||||||
|
});
|
||||||
|
audioUrl += "&static=true" + seekParam;
|
||||||
|
} else {
|
||||||
|
audioUrl = ApiClient.getUrl(mediaSource.TranscodingUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.startTimeTicksOffset = isDirectStream ? 0 : startPositionTicks;
|
||||||
|
}
|
||||||
|
|
||||||
var initialVolume = self.getSavedVolume();
|
var initialVolume = self.getSavedVolume();
|
||||||
|
|
||||||
|
|
|
@ -306,7 +306,13 @@
|
||||||
throw new Error("Url name cannot be empty");
|
throw new Error("Url name cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = serverAddress + "/" + name;
|
var url = serverAddress;
|
||||||
|
|
||||||
|
if (name.charAt(0) != '/') {
|
||||||
|
url += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
url += name;
|
||||||
|
|
||||||
if (params) {
|
if (params) {
|
||||||
url += "?" + AjaxApi.param(params);
|
url += "?" + AjaxApi.param(params);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue