2020-07-31 09:12:30 +01:00
|
|
|
import events from 'events';
|
|
|
|
import datetime from 'datetime';
|
|
|
|
import appSettings from 'appSettings';
|
|
|
|
import itemHelper from 'itemHelper';
|
|
|
|
import pluginManager from 'pluginManager';
|
|
|
|
import PlayQueueManager from 'playQueueManager';
|
|
|
|
import * as userSettings from 'userSettings';
|
|
|
|
import globalize from 'globalize';
|
|
|
|
import connectionManager from 'connectionManager';
|
|
|
|
import loading from 'loading';
|
|
|
|
import apphost from 'apphost';
|
|
|
|
import screenfull from 'screenfull';
|
|
|
|
|
|
|
|
function enableLocalPlaylistManagement(player) {
|
|
|
|
if (player.getPlaylist) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-07-30 10:15:25 +01:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (player.isLocalPlayer) {
|
|
|
|
return true;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return false;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function bindToFullscreenChange(player) {
|
|
|
|
if (screenfull.isEnabled) {
|
|
|
|
screenfull.on('change', function () {
|
|
|
|
events.trigger(player, 'fullscreenchange');
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// iOS Safari
|
|
|
|
document.addEventListener('webkitfullscreenchange', function () {
|
|
|
|
events.trigger(player, 'fullscreenchange');
|
|
|
|
}, false);
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function triggerPlayerChange(playbackManagerInstance, newPlayer, newTarget, previousPlayer, previousTargetInfo) {
|
|
|
|
if (!newPlayer && !previousPlayer) {
|
|
|
|
return;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (newTarget && previousTargetInfo) {
|
|
|
|
if (newTarget.id === previousTargetInfo.id) {
|
2019-01-10 15:39:37 +03:00
|
|
|
return;
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
events.trigger(playbackManagerInstance, 'playerchange', [newPlayer, newTarget, previousPlayer]);
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function reportPlayback(playbackManagerInstance, state, player, reportPlaylist, serverId, method, progressEventName) {
|
|
|
|
if (!serverId) {
|
|
|
|
// Not a server item
|
|
|
|
// We can expand on this later and possibly report them
|
|
|
|
events.trigger(playbackManagerInstance, 'reportplayback', [false]);
|
|
|
|
return;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const info = Object.assign({}, state.PlayState);
|
|
|
|
info.ItemId = state.NowPlayingItem.Id;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (progressEventName) {
|
|
|
|
info.EventName = progressEventName;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (reportPlaylist) {
|
|
|
|
addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId);
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = connectionManager.getApiClient(serverId);
|
|
|
|
const reportPlaybackPromise = apiClient[method](info);
|
|
|
|
// Notify that report has been sent
|
|
|
|
reportPlaybackPromise.then(() => {
|
|
|
|
events.trigger(playbackManagerInstance, 'reportplayback', [true]);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPlaylistSync(playbackManagerInstance, player) {
|
|
|
|
player = player || playbackManagerInstance._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.getPlaylistSync();
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return playbackManagerInstance._playQueueManager.getPlaylist();
|
|
|
|
}
|
|
|
|
|
|
|
|
function addPlaylistToPlaybackReport(playbackManagerInstance, info, player, serverId) {
|
|
|
|
info.NowPlayingQueue = getPlaylistSync(playbackManagerInstance, player).map(function (i) {
|
|
|
|
const itemInfo = {
|
|
|
|
Id: i.Id,
|
|
|
|
PlaylistItemId: i.PlaylistItemId
|
|
|
|
};
|
|
|
|
|
|
|
|
if (i.ServerId !== serverId) {
|
|
|
|
itemInfo.ServerId = i.ServerId;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return itemInfo;
|
|
|
|
});
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function normalizeName(t) {
|
|
|
|
return t.toLowerCase().replace(' ', '');
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function getItemsForPlayback(serverId, query) {
|
|
|
|
const apiClient = connectionManager.getApiClient(serverId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (query.Ids && query.Ids.split(',').length === 1) {
|
|
|
|
const itemId = query.Ids.split(',');
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) {
|
|
|
|
return {
|
|
|
|
Items: [item],
|
|
|
|
TotalRecordCount: 1
|
|
|
|
};
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
query.Limit = query.Limit || 300;
|
|
|
|
query.Fields = 'Chapters';
|
|
|
|
query.ExcludeLocationTypes = 'Virtual';
|
|
|
|
query.EnableTotalRecordCount = false;
|
|
|
|
query.CollapseBoxSetItems = false;
|
|
|
|
|
|
|
|
return apiClient.getItems(apiClient.getCurrentUserId(), query);
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function createStreamInfoFromUrlItem(item) {
|
|
|
|
// Check item.Path for games
|
|
|
|
return {
|
|
|
|
url: item.Url || item.Path,
|
|
|
|
playMethod: 'DirectPlay',
|
|
|
|
item: item,
|
|
|
|
textTracks: [],
|
|
|
|
mediaType: item.MediaType
|
|
|
|
};
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function mergePlaybackQueries(obj1, obj2) {
|
|
|
|
const query = Object.assign(obj1, obj2);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const filters = query.Filters ? query.Filters.split(',') : [];
|
|
|
|
if (filters.indexOf('IsNotFolder') === -1) {
|
|
|
|
filters.push('IsNotFolder');
|
|
|
|
}
|
|
|
|
query.Filters = filters.join(',');
|
|
|
|
return query;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function backdropImageUrl(apiClient, item, options) {
|
|
|
|
options = options || {};
|
|
|
|
options.type = options.type || 'Backdrop';
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
// If not resizing, get the original image
|
|
|
|
if (!options.maxWidth && !options.width && !options.maxHeight && !options.height) {
|
|
|
|
options.quality = 100;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (item.BackdropImageTags && item.BackdropImageTags.length) {
|
|
|
|
options.tag = item.BackdropImageTags[0];
|
|
|
|
return apiClient.getScaledImageUrl(item.Id, options);
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
|
|
|
|
options.tag = item.ParentBackdropImageTags[0];
|
|
|
|
return apiClient.getScaledImageUrl(item.ParentBackdropItemId, options);
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return null;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function getMimeType(type, container) {
|
|
|
|
container = (container || '').toLowerCase();
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (type === 'audio') {
|
|
|
|
if (container === 'opus') {
|
|
|
|
return 'audio/ogg';
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
if (container === 'webma') {
|
|
|
|
return 'audio/webm';
|
|
|
|
}
|
|
|
|
if (container === 'm4a') {
|
|
|
|
return 'audio/mp4';
|
|
|
|
}
|
|
|
|
} else if (type === 'video') {
|
|
|
|
if (container === 'mkv') {
|
|
|
|
return 'video/x-matroska';
|
|
|
|
}
|
|
|
|
if (container === 'm4v') {
|
|
|
|
return 'video/mp4';
|
|
|
|
}
|
|
|
|
if (container === 'mov') {
|
|
|
|
return 'video/quicktime';
|
|
|
|
}
|
|
|
|
if (container === 'mpg') {
|
|
|
|
return 'video/mpeg';
|
|
|
|
}
|
|
|
|
if (container === 'flv') {
|
|
|
|
return 'video/x-flv';
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return type + '/' + container;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function getParam(name, url) {
|
|
|
|
name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]');
|
|
|
|
const regexS = '[\\?&]' + name + '=([^&#]*)';
|
|
|
|
const regex = new RegExp(regexS, 'i');
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const results = regex.exec(url);
|
|
|
|
if (results == null) {
|
|
|
|
return '';
|
|
|
|
} else {
|
|
|
|
return decodeURIComponent(results[1].replace(/\+/g, ' '));
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function isAutomaticPlayer(player) {
|
|
|
|
if (player.isLocalPlayer) {
|
|
|
|
return true;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getAutomaticPlayers(instance, forceLocalPlayer) {
|
|
|
|
if (!forceLocalPlayer) {
|
|
|
|
const player = instance._currentPlayer;
|
|
|
|
if (player && !isAutomaticPlayer(player)) {
|
|
|
|
return [player];
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return instance.getPlayers().filter(isAutomaticPlayer);
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function isServerItem(item) {
|
|
|
|
if (!item.Id) {
|
2019-01-10 15:39:37 +03:00
|
|
|
return false;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
return true;
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function enableIntros(item) {
|
|
|
|
if (item.MediaType !== 'Video') {
|
|
|
|
return false;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
if (item.Type === 'TvChannel') {
|
|
|
|
return false;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
// disable for in-progress recordings
|
|
|
|
if (item.Status === 'InProgress') {
|
|
|
|
return false;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return isServerItem(item);
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function getIntros(firstItem, apiClient, options) {
|
|
|
|
if (options.startPositionTicks || options.startIndex || options.fullscreen === false || !enableIntros(firstItem) || !userSettings.enableCinemaMode()) {
|
|
|
|
return Promise.resolve({
|
|
|
|
Items: []
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return apiClient.getIntros(firstItem.Id).then(function (result) {
|
|
|
|
return result;
|
|
|
|
}, function (err) {
|
|
|
|
return Promise.resolve({
|
|
|
|
Items: []
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
2020-07-31 09:12:30 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getAudioMaxValues(deviceProfile) {
|
|
|
|
// TODO - this could vary per codec and should be done on the server using the entire profile
|
|
|
|
let maxAudioSampleRate = null;
|
|
|
|
let maxAudioBitDepth = null;
|
|
|
|
let maxAudioBitrate = null;
|
|
|
|
|
|
|
|
deviceProfile.CodecProfiles.map(function (codecProfile) {
|
|
|
|
if (codecProfile.Type === 'Audio') {
|
|
|
|
(codecProfile.Conditions || []).map(function (condition) {
|
|
|
|
if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitDepth') {
|
|
|
|
return maxAudioBitDepth = condition.Value;
|
|
|
|
} else if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioSampleRate') {
|
|
|
|
return maxAudioSampleRate = condition.Value;
|
|
|
|
} else if (condition.Condition === 'LessThanEqual' && condition.Property === 'AudioBitrate') {
|
|
|
|
return maxAudioBitrate = condition.Value;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return {
|
|
|
|
maxAudioSampleRate: maxAudioSampleRate,
|
|
|
|
maxAudioBitDepth: maxAudioBitDepth,
|
|
|
|
maxAudioBitrate: maxAudioBitrate
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let startingPlaySession = new Date().getTime();
|
|
|
|
function getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxAudioSampleRate, maxAudioBitDepth, maxAudioBitrate, startPosition) {
|
|
|
|
const url = 'Audio/' + item.Id + '/universal';
|
|
|
|
|
|
|
|
startingPlaySession++;
|
|
|
|
return apiClient.getUrl(url, {
|
|
|
|
UserId: apiClient.getCurrentUserId(),
|
|
|
|
DeviceId: apiClient.deviceId(),
|
|
|
|
MaxStreamingBitrate: maxAudioBitrate || maxBitrate,
|
|
|
|
Container: directPlayContainers,
|
|
|
|
TranscodingContainer: transcodingProfile.Container || null,
|
|
|
|
TranscodingProtocol: transcodingProfile.Protocol || null,
|
|
|
|
AudioCodec: transcodingProfile.AudioCodec,
|
|
|
|
MaxAudioSampleRate: maxAudioSampleRate,
|
|
|
|
MaxAudioBitDepth: maxAudioBitDepth,
|
|
|
|
api_key: apiClient.accessToken(),
|
|
|
|
PlaySessionId: startingPlaySession,
|
|
|
|
StartTimeTicks: startPosition || 0,
|
|
|
|
EnableRedirection: true,
|
|
|
|
EnableRemoteMedia: apphost.supports('remoteaudio')
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition) {
|
|
|
|
const transcodingProfile = deviceProfile.TranscodingProfiles.filter(function (p) {
|
|
|
|
return p.Type === 'Audio' && p.Context === 'Streaming';
|
|
|
|
})[0];
|
|
|
|
|
|
|
|
let directPlayContainers = '';
|
|
|
|
|
|
|
|
deviceProfile.DirectPlayProfiles.map(function (p) {
|
|
|
|
if (p.Type === 'Audio') {
|
|
|
|
if (directPlayContainers) {
|
|
|
|
directPlayContainers += ',' + p.Container;
|
|
|
|
} else {
|
|
|
|
directPlayContainers = p.Container;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (p.AudioCodec) {
|
|
|
|
directPlayContainers += '|' + p.AudioCodec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const maxValues = getAudioMaxValues(deviceProfile);
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return getAudioStreamUrl(item, transcodingProfile, directPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition);
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) {
|
|
|
|
const audioTranscodingProfile = deviceProfile.TranscodingProfiles.filter(function (p) {
|
|
|
|
return p.Type === 'Audio' && p.Context === 'Streaming';
|
|
|
|
})[0];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let audioDirectPlayContainers = '';
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
deviceProfile.DirectPlayProfiles.map(function (p) {
|
|
|
|
if (p.Type === 'Audio') {
|
|
|
|
if (audioDirectPlayContainers) {
|
|
|
|
audioDirectPlayContainers += ',' + p.Container;
|
|
|
|
} else {
|
|
|
|
audioDirectPlayContainers = p.Container;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (p.AudioCodec) {
|
|
|
|
audioDirectPlayContainers += '|' + p.AudioCodec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const maxValues = getAudioMaxValues(deviceProfile);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const streamUrls = [];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
for (let i = 0, length = items.length; i < length; i++) {
|
|
|
|
const item = items[i];
|
|
|
|
let streamUrl;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (item.MediaType === 'Audio' && !itemHelper.isLocalItem(item)) {
|
|
|
|
streamUrl = getAudioStreamUrl(item, audioTranscodingProfile, audioDirectPlayContainers, maxBitrate, apiClient, maxValues.maxAudioSampleRate, maxValues.maxAudioBitDepth, maxValues.maxAudioBitrate, startPosition);
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
streamUrls.push(streamUrl || '');
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (i === 0) {
|
|
|
|
startPosition = 0;
|
|
|
|
}
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return Promise.resolve(streamUrls);
|
|
|
|
}
|
|
|
|
|
|
|
|
function setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition) {
|
|
|
|
return getStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPosition).then(function (streamUrls) {
|
|
|
|
for (let i = 0, length = items.length; i < length; i++) {
|
|
|
|
const item = items[i];
|
|
|
|
const streamUrl = streamUrls[i];
|
|
|
|
|
|
|
|
if (streamUrl) {
|
|
|
|
item.PresetMediaSource = {
|
|
|
|
StreamUrl: streamUrl,
|
|
|
|
Id: item.Id,
|
|
|
|
MediaStreams: [],
|
|
|
|
RunTimeTicks: item.RunTimeTicks
|
|
|
|
};
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPlaybackInfo(player,
|
|
|
|
apiClient,
|
|
|
|
item,
|
|
|
|
deviceProfile,
|
|
|
|
maxBitrate,
|
|
|
|
startPosition,
|
|
|
|
isPlayback,
|
|
|
|
mediaSourceId,
|
|
|
|
audioStreamIndex,
|
|
|
|
subtitleStreamIndex,
|
|
|
|
liveStreamId,
|
|
|
|
enableDirectPlay,
|
|
|
|
enableDirectStream,
|
|
|
|
allowVideoStreamCopy,
|
|
|
|
allowAudioStreamCopy) {
|
|
|
|
if (!itemHelper.isLocalItem(item) && item.MediaType === 'Audio') {
|
|
|
|
return Promise.resolve({
|
|
|
|
MediaSources: [
|
|
|
|
{
|
|
|
|
StreamUrl: getAudioStreamUrlFromDeviceProfile(item, deviceProfile, maxBitrate, apiClient, startPosition),
|
|
|
|
Id: item.Id,
|
|
|
|
MediaStreams: [],
|
|
|
|
RunTimeTicks: item.RunTimeTicks
|
|
|
|
}]
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (item.PresetMediaSource) {
|
|
|
|
return Promise.resolve({
|
|
|
|
MediaSources: [item.PresetMediaSource]
|
|
|
|
});
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const itemId = item.Id;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const query = {
|
|
|
|
UserId: apiClient.getCurrentUserId(),
|
|
|
|
StartTimeTicks: startPosition || 0
|
|
|
|
};
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (isPlayback) {
|
|
|
|
query.IsPlayback = true;
|
|
|
|
query.AutoOpenLiveStream = true;
|
|
|
|
} else {
|
|
|
|
query.IsPlayback = false;
|
|
|
|
query.AutoOpenLiveStream = false;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (audioStreamIndex != null) {
|
|
|
|
query.AudioStreamIndex = audioStreamIndex;
|
|
|
|
}
|
|
|
|
if (subtitleStreamIndex != null) {
|
|
|
|
query.SubtitleStreamIndex = subtitleStreamIndex;
|
|
|
|
}
|
|
|
|
if (enableDirectPlay != null) {
|
|
|
|
query.EnableDirectPlay = enableDirectPlay;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (enableDirectStream != null) {
|
|
|
|
query.EnableDirectStream = enableDirectStream;
|
|
|
|
}
|
|
|
|
if (allowVideoStreamCopy != null) {
|
|
|
|
query.AllowVideoStreamCopy = allowVideoStreamCopy;
|
|
|
|
}
|
|
|
|
if (allowAudioStreamCopy != null) {
|
|
|
|
query.AllowAudioStreamCopy = allowAudioStreamCopy;
|
|
|
|
}
|
|
|
|
if (mediaSourceId) {
|
|
|
|
query.MediaSourceId = mediaSourceId;
|
|
|
|
}
|
|
|
|
if (liveStreamId) {
|
|
|
|
query.LiveStreamId = liveStreamId;
|
|
|
|
}
|
|
|
|
if (maxBitrate) {
|
|
|
|
query.MaxStreamingBitrate = maxBitrate;
|
|
|
|
}
|
|
|
|
if (player.enableMediaProbe && !player.enableMediaProbe(item)) {
|
|
|
|
query.EnableMediaProbe = false;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
// lastly, enforce player overrides for special situations
|
|
|
|
if (query.EnableDirectStream !== false) {
|
|
|
|
if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) {
|
|
|
|
query.EnableDirectStream = false;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (player.getDirectPlayProtocols) {
|
|
|
|
query.DirectPlayProtocols = player.getDirectPlayProtocols();
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return apiClient.getPlaybackInfo(itemId, query, deviceProfile);
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function getOptimalMediaSource(apiClient, item, versions) {
|
|
|
|
const promises = versions.map(function (v) {
|
|
|
|
return supportsDirectPlay(apiClient, item, v);
|
|
|
|
});
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (!promises.length) {
|
|
|
|
return Promise.reject();
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return Promise.all(promises).then(function (results) {
|
|
|
|
for (let i = 0, length = versions.length; i < length; i++) {
|
|
|
|
versions[i].enableDirectPlay = results[i] || false;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
let optimalVersion = versions.filter(function (v) {
|
|
|
|
return v.enableDirectPlay;
|
|
|
|
})[0];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (!optimalVersion) {
|
|
|
|
optimalVersion = versions.filter(function (v) {
|
|
|
|
return v.SupportsDirectStream;
|
2018-10-23 01:05:09 +03:00
|
|
|
})[0];
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
optimalVersion = optimalVersion || versions.filter(function (s) {
|
|
|
|
return s.SupportsTranscoding;
|
|
|
|
})[0];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return optimalVersion || versions[0];
|
|
|
|
});
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, maxBitrate, startPosition, mediaSource, audioStreamIndex, subtitleStreamIndex) {
|
|
|
|
const postData = {
|
|
|
|
DeviceProfile: deviceProfile,
|
|
|
|
OpenToken: mediaSource.OpenToken
|
|
|
|
};
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const query = {
|
|
|
|
UserId: apiClient.getCurrentUserId(),
|
|
|
|
StartTimeTicks: startPosition || 0,
|
|
|
|
ItemId: item.Id,
|
|
|
|
PlaySessionId: playSessionId
|
|
|
|
};
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (maxBitrate) {
|
|
|
|
query.MaxStreamingBitrate = maxBitrate;
|
|
|
|
}
|
|
|
|
if (audioStreamIndex != null) {
|
|
|
|
query.AudioStreamIndex = audioStreamIndex;
|
|
|
|
}
|
|
|
|
if (subtitleStreamIndex != null) {
|
|
|
|
query.SubtitleStreamIndex = subtitleStreamIndex;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
// lastly, enforce player overrides for special situations
|
|
|
|
if (query.EnableDirectStream !== false) {
|
|
|
|
if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) {
|
|
|
|
query.EnableDirectStream = false;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return apiClient.ajax({
|
|
|
|
url: apiClient.getUrl('LiveStreams/Open', query),
|
|
|
|
type: 'POST',
|
|
|
|
data: JSON.stringify(postData),
|
|
|
|
contentType: 'application/json',
|
|
|
|
dataType: 'json'
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
});
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function isHostReachable(mediaSource, apiClient) {
|
|
|
|
if (mediaSource.IsRemote) {
|
|
|
|
return Promise.resolve(true);
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return apiClient.getEndpointInfo().then(function (endpointInfo) {
|
|
|
|
if (endpointInfo.IsInNetwork) {
|
|
|
|
if (!endpointInfo.IsLocal) {
|
|
|
|
const path = (mediaSource.Path || '').toLowerCase();
|
|
|
|
if (path.indexOf('localhost') !== -1 || path.indexOf('127.0.0.1') !== -1) {
|
|
|
|
// This will only work if the app is on the same machine as the server
|
|
|
|
return Promise.resolve(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
return Promise.resolve(true);
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
// media source is in network, but connection is out of network
|
|
|
|
return Promise.resolve(false);
|
|
|
|
});
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function supportsDirectPlay(apiClient, item, mediaSource) {
|
|
|
|
// folder rip hacks due to not yet being supported by the stream building engine
|
|
|
|
const isFolderRip = mediaSource.VideoType === 'BluRay' || mediaSource.VideoType === 'Dvd' || mediaSource.VideoType === 'HdDvd';
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (mediaSource.SupportsDirectPlay || isFolderRip) {
|
|
|
|
if (mediaSource.IsRemote && !apphost.supports('remotevideo')) {
|
2019-01-10 15:39:37 +03:00
|
|
|
return Promise.resolve(false);
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (mediaSource.Protocol === 'Http' && !mediaSource.RequiredHttpHeaders.length) {
|
|
|
|
// If this is the only way it can be played, then allow it
|
|
|
|
if (!mediaSource.SupportsDirectStream && !mediaSource.SupportsTranscoding) {
|
|
|
|
return Promise.resolve(true);
|
|
|
|
} else {
|
|
|
|
return isHostReachable(mediaSource, apiClient);
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
} else if (mediaSource.Protocol === 'File') {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
// Determine if the file can be accessed directly
|
2020-08-02 09:27:03 +01:00
|
|
|
import('filesystem').then((filesystem) => {
|
2020-07-31 09:12:30 +01:00
|
|
|
const method = isFolderRip ?
|
|
|
|
'directoryExists' :
|
|
|
|
'fileExists';
|
|
|
|
|
|
|
|
filesystem[method](mediaSource.Path).then(function () {
|
|
|
|
resolve(true);
|
|
|
|
}, function () {
|
|
|
|
resolve(false);
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
|
|
|
});
|
2020-07-31 09:12:30 +01:00
|
|
|
});
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return Promise.resolve(false);
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function validatePlaybackInfoResult(instance, result) {
|
|
|
|
if (result.ErrorCode) {
|
|
|
|
showPlaybackInfoErrorMessage(instance, result.ErrorCode);
|
|
|
|
return false;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return true;
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function showPlaybackInfoErrorMessage(instance, errorCode, playNextTrack) {
|
|
|
|
import('alert').then(({ default: alert }) => {
|
|
|
|
alert({
|
|
|
|
text: globalize.translate('PlaybackError' + errorCode),
|
|
|
|
title: globalize.translate('HeaderPlaybackError')
|
|
|
|
}).then(function () {
|
|
|
|
if (playNextTrack) {
|
|
|
|
instance.nextTrack();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function normalizePlayOptions(playOptions) {
|
|
|
|
playOptions.fullscreen = playOptions.fullscreen !== false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function truncatePlayOptions(playOptions) {
|
|
|
|
return {
|
|
|
|
fullscreen: playOptions.fullscreen,
|
|
|
|
mediaSourceId: playOptions.mediaSourceId,
|
|
|
|
audioStreamIndex: playOptions.audioStreamIndex,
|
|
|
|
subtitleStreamIndex: playOptions.subtitleStreamIndex,
|
|
|
|
startPositionTicks: playOptions.startPositionTicks
|
|
|
|
};
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function getNowPlayingItemForReporting(player, item, mediaSource) {
|
|
|
|
const nowPlayingItem = Object.assign({}, item);
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
if (mediaSource) {
|
|
|
|
nowPlayingItem.RunTimeTicks = mediaSource.RunTimeTicks;
|
|
|
|
nowPlayingItem.MediaStreams = mediaSource.MediaStreams;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
// not needed
|
|
|
|
nowPlayingItem.MediaSources = null;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
nowPlayingItem.RunTimeTicks = nowPlayingItem.RunTimeTicks || player.duration() * 10000;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return nowPlayingItem;
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function displayPlayerIndividually(player) {
|
|
|
|
return !player.isLocalPlayer;
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function createTarget(instance, player) {
|
|
|
|
return {
|
|
|
|
name: player.name,
|
|
|
|
id: player.id,
|
|
|
|
playerName: player.name,
|
|
|
|
playableMediaTypes: ['Audio', 'Video', 'Photo', 'Book'].map(player.canPlayMediaType),
|
|
|
|
isLocalPlayer: player.isLocalPlayer,
|
|
|
|
supportedCommands: instance.getSupportedCommands(player)
|
|
|
|
};
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function getPlayerTargets(player) {
|
|
|
|
if (player.getTargets) {
|
|
|
|
return player.getTargets();
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return Promise.resolve([createTarget(player)]);
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
function sortPlayerTargets(a, b) {
|
|
|
|
let aVal = a.isLocalPlayer ? 0 : 1;
|
|
|
|
let bVal = b.isLocalPlayer ? 0 : 1;
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
aVal = aVal.toString() + a.name;
|
|
|
|
bVal = bVal.toString() + b.name;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
return aVal.localeCompare(bVal);
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
class PlaybackManager {
|
|
|
|
constructor() {
|
|
|
|
const self = this;
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const players = [];
|
|
|
|
let currentTargetInfo;
|
|
|
|
let currentPairingId = null;
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
this._playNextAfterEnded = true;
|
2020-07-31 09:12:30 +01:00
|
|
|
const playerStates = {};
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
this._playQueueManager = new PlayQueueManager();
|
|
|
|
|
|
|
|
self.currentItem = function (player) {
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player.currentItem) {
|
|
|
|
return player.currentItem();
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const data = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
return data.streamInfo ? data.streamInfo.item : null;
|
|
|
|
};
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
self.currentMediaSource = function (player) {
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player.currentMediaSource) {
|
|
|
|
return player.currentMediaSource();
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const data = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
return data.streamInfo ? data.streamInfo.mediaSource : null;
|
|
|
|
};
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
self.playMethod = function (player) {
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player.playMethod) {
|
|
|
|
return player.playMethod();
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const data = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
return data.streamInfo ? data.streamInfo.playMethod : null;
|
|
|
|
};
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
self.playSessionId = function (player) {
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player.playSessionId) {
|
|
|
|
return player.playSessionId();
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const data = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
return data.streamInfo ? data.streamInfo.playSessionId : null;
|
|
|
|
};
|
|
|
|
|
|
|
|
self.getPlayerInfo = function () {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = self._currentPlayer;
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
if (!player) {
|
|
|
|
return null;
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const target = currentTargetInfo || {};
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
return {
|
|
|
|
name: player.name,
|
|
|
|
isLocalPlayer: player.isLocalPlayer,
|
|
|
|
id: target.id,
|
|
|
|
deviceName: target.deviceName,
|
|
|
|
playableMediaTypes: target.playableMediaTypes,
|
|
|
|
supportedCommands: target.supportedCommands
|
|
|
|
};
|
|
|
|
};
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
self.setActivePlayer = function (player, targetInfo) {
|
|
|
|
if (player === 'localplayer' || player.name === 'localplayer') {
|
|
|
|
if (self._currentPlayer && self._currentPlayer.isLocalPlayer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setCurrentPlayerInternal(null, null);
|
|
|
|
return;
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
if (typeof (player) === 'string') {
|
|
|
|
player = players.filter(function (p) {
|
|
|
|
return p.name === player;
|
|
|
|
})[0];
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
if (!player) {
|
|
|
|
throw new Error('null player');
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
setCurrentPlayerInternal(player, targetInfo);
|
|
|
|
};
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
self.trySetActivePlayer = function (player, targetInfo) {
|
|
|
|
if (player === 'localplayer' || player.name === 'localplayer') {
|
|
|
|
if (self._currentPlayer && self._currentPlayer.isLocalPlayer) {
|
|
|
|
return;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
return;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
if (typeof (player) === 'string') {
|
|
|
|
player = players.filter(function (p) {
|
|
|
|
return p.name === player;
|
|
|
|
})[0];
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
if (!player) {
|
|
|
|
throw new Error('null player');
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
if (currentPairingId === targetInfo.id) {
|
|
|
|
return;
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
currentPairingId = targetInfo.id;
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const promise = player.tryPair ?
|
2019-01-10 15:39:37 +03:00
|
|
|
player.tryPair(targetInfo) :
|
|
|
|
Promise.resolve();
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
events.trigger(self, 'pairing');
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
promise.then(function () {
|
|
|
|
events.trigger(self, 'paired');
|
|
|
|
setCurrentPlayerInternal(player, targetInfo);
|
|
|
|
}, function () {
|
|
|
|
events.trigger(self, 'pairerror');
|
|
|
|
if (currentPairingId === targetInfo.id) {
|
|
|
|
currentPairingId = null;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
|
|
|
};
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
self.getTargets = function () {
|
2020-07-31 09:12:30 +01:00
|
|
|
const promises = players.filter(displayPlayerIndividually).map(getPlayerTargets);
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
return Promise.all(promises).then(function (responses) {
|
|
|
|
return connectionManager.currentApiClient().getCurrentUser().then(function (user) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const targets = [];
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
targets.push({
|
2019-02-03 02:41:16 +09:00
|
|
|
name: globalize.translate('HeaderMyDevice'),
|
2019-01-10 15:39:37 +03:00
|
|
|
id: 'localplayer',
|
|
|
|
playerName: 'localplayer',
|
2019-01-27 22:10:07 +01:00
|
|
|
playableMediaTypes: ['Audio', 'Video', 'Photo', 'Book'],
|
2019-01-10 15:39:37 +03:00
|
|
|
isLocalPlayer: true,
|
|
|
|
supportedCommands: self.getSupportedCommands({
|
|
|
|
isLocalPlayer: true
|
|
|
|
}),
|
|
|
|
user: user
|
|
|
|
});
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
for (let i = 0; i < responses.length; i++) {
|
|
|
|
const subTargets = responses[i];
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
for (let j = 0; j < subTargets.length; j++) {
|
2019-01-10 15:39:37 +03:00
|
|
|
targets.push(subTargets[j]);
|
|
|
|
}
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2020-07-30 19:42:30 +02:00
|
|
|
return targets.sort(sortPlayerTargets);
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
function getCurrentSubtitleStream(player) {
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const index = getPlayerData(player).subtitleStreamIndex;
|
2018-10-23 01:05:09 +03:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
if (index == null || index === -1) {
|
|
|
|
return null;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
return getSubtitleStream(player, index);
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
function getSubtitleStream(player, index) {
|
|
|
|
return self.subtitleTracks(player).filter(function (s) {
|
|
|
|
return s.Type === 'Subtitle' && s.Index === index;
|
|
|
|
})[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
self.getPlaylist = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
if (player.getPlaylistSync) {
|
|
|
|
return Promise.resolve(player.getPlaylistSync());
|
|
|
|
}
|
|
|
|
|
|
|
|
return player.getPlaylist();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.resolve(self._playQueueManager.getPlaylist());
|
|
|
|
};
|
|
|
|
|
|
|
|
function removeCurrentPlayer(player) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const previousPlayer = self._currentPlayer;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (!previousPlayer || player.id === previousPlayer.id) {
|
|
|
|
setCurrentPlayerInternal(null);
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
function setCurrentPlayerInternal(player, targetInfo) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const previousPlayer = self._currentPlayer;
|
|
|
|
const previousTargetInfo = currentTargetInfo;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (player && !targetInfo && player.isLocalPlayer) {
|
|
|
|
targetInfo = createTarget(self, player);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (player && !targetInfo) {
|
|
|
|
throw new Error('targetInfo cannot be null');
|
|
|
|
}
|
|
|
|
|
2018-10-23 01:05:09 +03:00
|
|
|
currentPairingId = null;
|
2019-01-10 15:39:37 +03:00
|
|
|
self._currentPlayer = player;
|
|
|
|
currentTargetInfo = targetInfo;
|
|
|
|
|
|
|
|
if (targetInfo) {
|
2020-02-16 03:44:43 +01:00
|
|
|
console.debug('Active player: ' + JSON.stringify(targetInfo));
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (previousPlayer) {
|
|
|
|
self.endPlayerUpdates(previousPlayer);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (player) {
|
|
|
|
self.beginPlayerUpdates(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
triggerPlayerChange(self, player, targetInfo, previousPlayer, previousTargetInfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.isPlaying = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player) {
|
|
|
|
if (player.isPlaying) {
|
|
|
|
return player.isPlaying();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return player != null && player.currentSrc() != null;
|
|
|
|
};
|
|
|
|
|
|
|
|
self.isPlayingMediaType = function (mediaType, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player) {
|
|
|
|
if (player.isPlaying) {
|
|
|
|
return player.isPlaying(mediaType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-23 01:05:09 +03:00
|
|
|
if (self.isPlaying(player)) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const playerData = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
return playerData.streamInfo.mediaType === mediaType;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
self.isPlayingLocally = function (mediaTypes, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (!player || !player.isLocalPlayer) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return mediaTypes.filter(function (mediaType) {
|
|
|
|
return self.isPlayingMediaType(mediaType, player);
|
|
|
|
}).length > 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
self.isPlayingVideo = function (player) {
|
|
|
|
return self.isPlayingMediaType('Video', player);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.isPlayingAudio = function (player) {
|
|
|
|
return self.isPlayingMediaType('Audio', player);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.getPlayers = function () {
|
|
|
|
return players;
|
|
|
|
};
|
|
|
|
|
|
|
|
function getDefaultPlayOptions() {
|
|
|
|
return {
|
|
|
|
fullscreen: true
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
self.canPlay = function (item) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const itemType = item.Type;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-05-04 12:44:12 +02:00
|
|
|
if (itemType === 'PhotoAlbum' || itemType === 'MusicGenre' || itemType === 'Season' || itemType === 'Series' || itemType === 'BoxSet' || itemType === 'MusicAlbum' || itemType === 'MusicArtist' || itemType === 'Playlist') {
|
2019-01-10 15:39:37 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-05-04 12:44:12 +02:00
|
|
|
if (item.LocationType === 'Virtual') {
|
|
|
|
if (itemType !== 'Program') {
|
2019-01-10 15:39:37 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-04 12:44:12 +02:00
|
|
|
if (itemType === 'Program') {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (!item.EndDate || !item.StartDate) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (new Date().getTime() > datetime.parseISO8601Date(item.EndDate).getTime() || new Date().getTime() < datetime.parseISO8601Date(item.StartDate).getTime()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//var mediaType = item.MediaType;
|
|
|
|
return getPlayer(item, getDefaultPlayOptions()) != null;
|
|
|
|
};
|
|
|
|
|
|
|
|
self.toggleAspectRatio = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const current = self.getAspectRatio(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const supported = self.getSupportedAspectRatios(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let index = -1;
|
|
|
|
for (let i = 0, length = supported.length; i < length; i++) {
|
2018-10-23 01:05:09 +03:00
|
|
|
if (supported[i].id === current) {
|
|
|
|
index = i;
|
2019-01-10 15:39:37 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
index++;
|
|
|
|
if (index >= supported.length) {
|
|
|
|
index = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.setAspectRatio(supported[index].id, player);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.setAspectRatio = function (val, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player && player.setAspectRatio) {
|
|
|
|
player.setAspectRatio(val);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.getSupportedAspectRatios = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player && player.getSupportedAspectRatios) {
|
|
|
|
return player.getSupportedAspectRatios();
|
|
|
|
}
|
|
|
|
|
|
|
|
return [];
|
|
|
|
};
|
|
|
|
|
|
|
|
self.getAspectRatio = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player && player.getAspectRatio) {
|
|
|
|
return player.getAspectRatio();
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
};
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let brightnessOsdLoaded;
|
2019-01-10 15:39:37 +03:00
|
|
|
self.setBrightness = function (val, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
2018-10-23 01:05:09 +03:00
|
|
|
if (player) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (!brightnessOsdLoaded) {
|
|
|
|
brightnessOsdLoaded = true;
|
|
|
|
// TODO: Have this trigger an event instead to get the osd out of here
|
2020-07-31 09:12:30 +01:00
|
|
|
import('brightnessOsd').then();
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
player.setBrightness(val);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.getBrightness = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player) {
|
|
|
|
return player.getBrightness();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.setVolume = function (val, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player) {
|
|
|
|
player.setVolume(val);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.getVolume = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player) {
|
|
|
|
return player.getVolume();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.volumeUp = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player) {
|
|
|
|
player.volumeUp();
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
self.volumeDown = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
2018-10-23 01:05:09 +03:00
|
|
|
if (player) {
|
2019-01-10 15:39:37 +03:00
|
|
|
player.volumeDown();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.changeAudioStream = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.changeAudioStream();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!player) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const currentMediaSource = self.currentMediaSource(player);
|
|
|
|
const mediaStreams = [];
|
|
|
|
for (let i = 0, length = currentMediaSource.MediaStreams.length; i < length; i++) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (currentMediaSource.MediaStreams[i].Type === 'Audio') {
|
|
|
|
mediaStreams.push(currentMediaSource.MediaStreams[i]);
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
// Nothing to change
|
|
|
|
if (mediaStreams.length <= 1) {
|
|
|
|
return;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const currentStreamIndex = self.getAudioStreamIndex(player);
|
|
|
|
let indexInList = -1;
|
|
|
|
for (let i = 0, length = mediaStreams.length; i < length; i++) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (mediaStreams[i].Index === currentStreamIndex) {
|
|
|
|
indexInList = i;
|
|
|
|
break;
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let nextIndex = indexInList + 1;
|
2019-01-10 15:39:37 +03:00
|
|
|
if (nextIndex >= mediaStreams.length) {
|
|
|
|
nextIndex = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
nextIndex = nextIndex === -1 ? -1 : mediaStreams[nextIndex].Index;
|
|
|
|
|
|
|
|
self.setAudioStreamIndex(nextIndex, player);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.changeSubtitleStream = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.changeSubtitleStream();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!player) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const currentMediaSource = self.currentMediaSource(player);
|
|
|
|
const mediaStreams = [];
|
|
|
|
for (let i = 0, length = currentMediaSource.MediaStreams.length; i < length; i++) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (currentMediaSource.MediaStreams[i].Type === 'Subtitle') {
|
|
|
|
mediaStreams.push(currentMediaSource.MediaStreams[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No known streams, nothing to change
|
|
|
|
if (!mediaStreams.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const currentStreamIndex = self.getSubtitleStreamIndex(player);
|
|
|
|
let indexInList = -1;
|
|
|
|
for (let i = 0, length = mediaStreams.length; i < length; i++) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (mediaStreams[i].Index === currentStreamIndex) {
|
|
|
|
indexInList = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let nextIndex = indexInList + 1;
|
2019-01-10 15:39:37 +03:00
|
|
|
if (nextIndex >= mediaStreams.length) {
|
|
|
|
nextIndex = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
nextIndex = nextIndex === -1 ? -1 : mediaStreams[nextIndex].Index;
|
|
|
|
|
|
|
|
self.setSubtitleStreamIndex(nextIndex, player);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.getAudioStreamIndex = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.getAudioStreamIndex();
|
|
|
|
}
|
|
|
|
|
|
|
|
return getPlayerData(player).audioStreamIndex;
|
|
|
|
};
|
|
|
|
|
|
|
|
function isAudioStreamSupported(mediaSource, index, deviceProfile) {
|
2020-07-31 09:12:30 +01:00
|
|
|
let mediaStream;
|
|
|
|
const mediaStreams = mediaSource.MediaStreams;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
for (let i = 0, length = mediaStreams.length; i < length; i++) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (mediaStreams[i].Type === 'Audio' && mediaStreams[i].Index === index) {
|
|
|
|
mediaStream = mediaStreams[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mediaStream) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const codec = (mediaStream.Codec || '').toLowerCase();
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (!codec) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const profiles = deviceProfile.DirectPlayProfiles || [];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
return profiles.filter(function (p) {
|
|
|
|
if (p.Type === 'Video') {
|
|
|
|
if (!p.AudioCodec) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is an exclusion filter
|
|
|
|
if (p.AudioCodec.indexOf('-') === 0) {
|
|
|
|
return p.AudioCodec.toLowerCase().indexOf(codec) === -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return p.AudioCodec.toLowerCase().indexOf(codec) !== -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}).length > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.setAudioStreamIndex = function (index, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.setAudioStreamIndex(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self.playMethod(player) === 'Transcode' || !player.canSetAudioStreamIndex()) {
|
|
|
|
changeStream(player, getCurrentTicks(player), { AudioStreamIndex: index });
|
|
|
|
getPlayerData(player).audioStreamIndex = index;
|
|
|
|
} else {
|
|
|
|
// See if the player supports the track without transcoding
|
|
|
|
player.getDeviceProfile(self.currentItem(player)).then(function (profile) {
|
|
|
|
if (isAudioStreamSupported(self.currentMediaSource(player), index, profile)) {
|
|
|
|
player.setAudioStreamIndex(index);
|
|
|
|
getPlayerData(player).audioStreamIndex = index;
|
2019-11-23 00:29:38 +09:00
|
|
|
} else {
|
2019-01-10 15:39:37 +03:00
|
|
|
changeStream(player, getCurrentTicks(player), { AudioStreamIndex: index });
|
|
|
|
getPlayerData(player).audioStreamIndex = index;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function getSavedMaxStreamingBitrate(apiClient, mediaType) {
|
|
|
|
if (!apiClient) {
|
|
|
|
// This should hopefully never happen
|
|
|
|
apiClient = connectionManager.currentApiClient();
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const endpointInfo = apiClient.getSavedEndpointInfo() || {};
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
return appSettings.maxStreamingBitrate(endpointInfo.IsInNetwork, mediaType);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.getMaxStreamingBitrate = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && player.getMaxStreamingBitrate) {
|
|
|
|
return player.getMaxStreamingBitrate();
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const playerData = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (playerData.maxStreamingBitrate) {
|
|
|
|
return playerData.maxStreamingBitrate;
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null;
|
|
|
|
const currentItem = self.currentItem(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = currentItem ? connectionManager.getApiClient(currentItem.ServerId) : connectionManager.currentApiClient();
|
2019-01-10 15:39:37 +03:00
|
|
|
return getSavedMaxStreamingBitrate(apiClient, mediaType);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.enableAutomaticBitrateDetection = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && player.enableAutomaticBitrateDetection) {
|
|
|
|
return player.enableAutomaticBitrateDetection();
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const playerData = getPlayerData(player);
|
|
|
|
const mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null;
|
|
|
|
const currentItem = self.currentItem(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = currentItem ? connectionManager.getApiClient(currentItem.ServerId) : connectionManager.currentApiClient();
|
|
|
|
const endpointInfo = apiClient.getSavedEndpointInfo() || {};
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
return appSettings.enableAutomaticBitrateDetection(endpointInfo.IsInNetwork, mediaType);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.setMaxStreamingBitrate = function (options, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && player.setMaxStreamingBitrate) {
|
|
|
|
return player.setMaxStreamingBitrate(options);
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = connectionManager.getApiClient(self.currentItem(player).ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
apiClient.getEndpointInfo().then(function (endpointInfo) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const playerData = getPlayerData(player);
|
|
|
|
const mediaType = playerData.streamInfo ? playerData.streamInfo.mediaType : null;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let promise;
|
2019-01-10 15:39:37 +03:00
|
|
|
if (options.enableAutomaticBitrateDetection) {
|
|
|
|
appSettings.enableAutomaticBitrateDetection(endpointInfo.IsInNetwork, mediaType, true);
|
|
|
|
promise = apiClient.detectBitrate(true);
|
|
|
|
} else {
|
|
|
|
appSettings.enableAutomaticBitrateDetection(endpointInfo.IsInNetwork, mediaType, false);
|
|
|
|
promise = Promise.resolve(options.maxBitrate);
|
|
|
|
}
|
|
|
|
|
|
|
|
promise.then(function (bitrate) {
|
|
|
|
appSettings.maxStreamingBitrate(endpointInfo.IsInNetwork, mediaType, bitrate);
|
|
|
|
|
|
|
|
changeStream(player, getCurrentTicks(player), {
|
|
|
|
MaxStreamingBitrate: bitrate
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
self.isFullscreen = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (!player.isLocalPlayer || player.isFullscreen) {
|
|
|
|
return player.isFullscreen();
|
|
|
|
}
|
|
|
|
|
2020-07-29 09:28:06 -04:00
|
|
|
if (!screenfull.isEnabled) {
|
|
|
|
// iOS Safari
|
|
|
|
return document.webkitIsFullScreen;
|
|
|
|
}
|
|
|
|
|
2020-04-15 13:08:48 +02:00
|
|
|
return screenfull.isFullscreen;
|
2019-01-10 15:39:37 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
self.toggleFullscreen = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (!player.isLocalPlayer || player.toggleFulscreen) {
|
|
|
|
return player.toggleFulscreen();
|
|
|
|
}
|
|
|
|
|
2020-04-15 13:08:48 +02:00
|
|
|
if (screenfull.isEnabled) {
|
|
|
|
screenfull.toggle();
|
2020-07-29 09:28:06 -04:00
|
|
|
} else {
|
|
|
|
// iOS Safari
|
|
|
|
if (document.webkitIsFullScreen && document.webkitCancelFullscreen) {
|
|
|
|
document.webkitCancelFullscreen();
|
|
|
|
} else {
|
|
|
|
const elem = document.querySelector('video');
|
|
|
|
if (elem && elem.webkitEnterFullscreen) {
|
|
|
|
elem.webkitEnterFullscreen();
|
|
|
|
}
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.togglePictureInPicture = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
return player.togglePictureInPicture();
|
|
|
|
};
|
|
|
|
|
2020-01-10 11:31:03 -05:00
|
|
|
self.toggleAirPlay = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
return player.toggleAirPlay();
|
|
|
|
};
|
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
self.getSubtitleStreamIndex = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.getSubtitleStreamIndex();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
|
|
|
}
|
|
|
|
|
|
|
|
return getPlayerData(player).subtitleStreamIndex;
|
|
|
|
};
|
|
|
|
|
|
|
|
function getDeliveryMethod(subtitleStream) {
|
|
|
|
// This will be null for internal subs for local items
|
|
|
|
if (subtitleStream.DeliveryMethod) {
|
|
|
|
return subtitleStream.DeliveryMethod;
|
|
|
|
}
|
|
|
|
|
|
|
|
return subtitleStream.IsExternal ? 'External' : 'Embed';
|
|
|
|
}
|
|
|
|
|
|
|
|
self.setSubtitleStreamIndex = function (index, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.setSubtitleStreamIndex(index);
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const currentStream = getCurrentSubtitleStream(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const newStream = getSubtitleStream(player, index);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (!currentStream && !newStream) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let selectedTrackElementIndex = -1;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const currentPlayMethod = self.playMethod(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (currentStream && !newStream) {
|
|
|
|
if (getDeliveryMethod(currentStream) === 'Encode' || (getDeliveryMethod(currentStream) === 'Embed' && currentPlayMethod === 'Transcode')) {
|
|
|
|
// Need to change the transcoded stream to remove subs
|
|
|
|
changeStream(player, getCurrentTicks(player), { SubtitleStreamIndex: -1 });
|
|
|
|
}
|
2019-11-23 00:29:38 +09:00
|
|
|
} else if (!currentStream && newStream) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (getDeliveryMethod(newStream) === 'External') {
|
|
|
|
selectedTrackElementIndex = index;
|
|
|
|
} else if (getDeliveryMethod(newStream) === 'Embed' && currentPlayMethod !== 'Transcode') {
|
|
|
|
selectedTrackElementIndex = index;
|
|
|
|
} else {
|
|
|
|
// Need to change the transcoded stream to add subs
|
|
|
|
changeStream(player, getCurrentTicks(player), { SubtitleStreamIndex: index });
|
|
|
|
}
|
2019-11-23 00:29:38 +09:00
|
|
|
} else if (currentStream && newStream) {
|
2019-01-10 15:39:37 +03:00
|
|
|
// Switching tracks
|
|
|
|
// We can handle this clientside if the new track is external or the new track is embedded and we're not transcoding
|
|
|
|
if (getDeliveryMethod(newStream) === 'External' || (getDeliveryMethod(newStream) === 'Embed' && currentPlayMethod !== 'Transcode')) {
|
|
|
|
selectedTrackElementIndex = index;
|
|
|
|
|
|
|
|
// But in order to handle this client side, if the previous track is being added via transcoding, we'll have to remove it
|
|
|
|
if (getDeliveryMethod(currentStream) !== 'External' && getDeliveryMethod(currentStream) !== 'Embed') {
|
|
|
|
changeStream(player, getCurrentTicks(player), { SubtitleStreamIndex: -1 });
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Need to change the transcoded stream to add subs
|
|
|
|
changeStream(player, getCurrentTicks(player), { SubtitleStreamIndex: index });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
player.setSubtitleStreamIndex(selectedTrackElementIndex);
|
|
|
|
|
|
|
|
getPlayerData(player).subtitleStreamIndex = index;
|
|
|
|
};
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
self.supportSubtitleOffset = function (player) {
|
2019-03-30 22:11:39 +01:00
|
|
|
player = player || self._currentPlayer;
|
2019-04-08 20:31:18 +02:00
|
|
|
return player && 'setSubtitleOffset' in player;
|
2020-04-05 13:48:10 +02:00
|
|
|
};
|
2019-04-08 20:31:18 +02:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
self.enableShowingSubtitleOffset = function (player) {
|
2019-04-08 20:31:18 +02:00
|
|
|
player = player || self._currentPlayer;
|
|
|
|
player.enableShowingSubtitleOffset();
|
2020-04-05 13:48:10 +02:00
|
|
|
};
|
2019-04-08 20:31:18 +02:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
self.disableShowingSubtitleOffset = function (player) {
|
2019-04-08 20:31:18 +02:00
|
|
|
player = player || self._currentPlayer;
|
2019-09-11 20:57:12 +02:00
|
|
|
if (player.disableShowingSubtitleOffset) {
|
|
|
|
player.disableShowingSubtitleOffset();
|
2019-11-23 00:29:38 +09:00
|
|
|
}
|
2020-04-05 13:48:10 +02:00
|
|
|
};
|
2019-04-08 20:31:18 +02:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
self.isShowingSubtitleOffsetEnabled = function (player) {
|
2019-04-08 20:31:18 +02:00
|
|
|
player = player || self._currentPlayer;
|
|
|
|
return player.isShowingSubtitleOffsetEnabled();
|
2020-04-05 13:48:10 +02:00
|
|
|
};
|
2019-03-30 22:11:39 +01:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
self.isSubtitleStreamExternal = function (index, player) {
|
|
|
|
const stream = getSubtitleStream(player, index);
|
2019-03-30 22:11:39 +01:00
|
|
|
return stream ? getDeliveryMethod(stream) === 'External' : false;
|
2020-04-05 13:48:10 +02:00
|
|
|
};
|
2019-03-30 22:11:39 +01:00
|
|
|
|
2019-04-08 20:31:18 +02:00
|
|
|
self.setSubtitleOffset = function (value, player) {
|
2019-03-30 22:11:39 +01:00
|
|
|
player = player || self._currentPlayer;
|
2019-09-11 20:57:12 +02:00
|
|
|
if (player.setSubtitleOffset) {
|
|
|
|
player.setSubtitleOffset(value);
|
|
|
|
}
|
2019-03-30 22:11:39 +01:00
|
|
|
};
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
self.getPlayerSubtitleOffset = function (player) {
|
2019-04-08 20:31:18 +02:00
|
|
|
player = player || self._currentPlayer;
|
2019-10-08 21:45:57 +02:00
|
|
|
if (player.getSubtitleOffset) {
|
2019-09-11 20:57:12 +02:00
|
|
|
return player.getSubtitleOffset();
|
|
|
|
}
|
2020-04-05 13:48:10 +02:00
|
|
|
};
|
2019-04-08 20:31:18 +02:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
self.canHandleOffsetOnCurrentSubtitle = function (player) {
|
|
|
|
const index = self.getSubtitleStreamIndex(player);
|
2019-11-23 00:29:38 +09:00
|
|
|
return index !== -1 && self.isSubtitleStreamExternal(index, player);
|
2020-04-05 13:48:10 +02:00
|
|
|
};
|
2019-03-30 22:11:39 +01:00
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
self.seek = function (ticks, player) {
|
|
|
|
ticks = Math.max(0, ticks);
|
|
|
|
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
if (player.isLocalPlayer) {
|
|
|
|
return player.seek((ticks || 0) / 10000);
|
|
|
|
} else {
|
|
|
|
return player.seek(ticks);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
changeStream(player, ticks);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.seekRelative = function (offsetTicks, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player) && player.seekRelative) {
|
|
|
|
if (player.isLocalPlayer) {
|
|
|
|
return player.seekRelative((ticks || 0) / 10000);
|
|
|
|
} else {
|
|
|
|
return player.seekRelative(ticks);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const ticks = getCurrentTicks(player) + offsetTicks;
|
2019-01-10 15:39:37 +03:00
|
|
|
return this.seek(ticks, player);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Returns true if the player can seek using native client-side seeking functions
|
|
|
|
function canPlayerSeek(player) {
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const playerData = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const currentSrc = (playerData.streamInfo.url || '').toLowerCase();
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (currentSrc.indexOf('.m3u8') !== -1) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (player.seekable) {
|
|
|
|
return player.seekable();
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const isPlayMethodTranscode = self.playMethod(player) === 'Transcode';
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (isPlayMethodTranscode) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return player.duration();
|
|
|
|
}
|
|
|
|
|
|
|
|
function changeStream(player, ticks, params) {
|
|
|
|
if (canPlayerSeek(player) && params == null) {
|
|
|
|
player.currentTime(parseInt(ticks / 10000));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
params = params || {};
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const liveStreamId = getPlayerData(player).streamInfo.liveStreamId;
|
|
|
|
const lastMediaInfoQuery = getPlayerData(player).streamInfo.lastMediaInfoQuery;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const playSessionId = self.playSessionId(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const currentItem = self.currentItem(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
player.getDeviceProfile(currentItem, {
|
|
|
|
isRetry: params.EnableDirectPlay === false
|
|
|
|
}).then(function (deviceProfile) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const audioStreamIndex = params.AudioStreamIndex == null ? getPlayerData(player).audioStreamIndex : params.AudioStreamIndex;
|
|
|
|
const subtitleStreamIndex = params.SubtitleStreamIndex == null ? getPlayerData(player).subtitleStreamIndex : params.SubtitleStreamIndex;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let currentMediaSource = self.currentMediaSource(player);
|
|
|
|
const apiClient = connectionManager.getApiClient(currentItem.ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (ticks) {
|
|
|
|
ticks = parseInt(ticks);
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const maxBitrate = params.MaxStreamingBitrate || self.getMaxStreamingBitrate(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const currentPlayOptions = currentItem.playOptions || getDefaultPlayOptions();
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
getPlaybackInfo(player, apiClient, currentItem, deviceProfile, maxBitrate, ticks, true, currentMediaSource.Id, audioStreamIndex, subtitleStreamIndex, liveStreamId, params.EnableDirectPlay, params.EnableDirectStream, params.AllowVideoStreamCopy, params.AllowAudioStreamCopy).then(function (result) {
|
|
|
|
if (validatePlaybackInfoResult(self, result)) {
|
|
|
|
currentMediaSource = result.MediaSources[0];
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const streamInfo = createStreamInfo(apiClient, currentItem.MediaType, currentItem, currentMediaSource, ticks);
|
2019-01-10 15:39:37 +03:00
|
|
|
streamInfo.fullscreen = currentPlayOptions.fullscreen;
|
|
|
|
streamInfo.lastMediaInfoQuery = lastMediaInfoQuery;
|
|
|
|
|
|
|
|
if (!streamInfo.url) {
|
|
|
|
showPlaybackInfoErrorMessage(self, 'NoCompatibleStream', true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
getPlayerData(player).subtitleStreamIndex = subtitleStreamIndex;
|
|
|
|
getPlayerData(player).audioStreamIndex = audioStreamIndex;
|
|
|
|
getPlayerData(player).maxStreamingBitrate = maxBitrate;
|
|
|
|
|
|
|
|
changeStreamToUrl(apiClient, player, playSessionId, streamInfo);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function changeStreamToUrl(apiClient, player, playSessionId, streamInfo, newPositionTicks) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const playerData = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
playerData.isChangingStream = true;
|
|
|
|
|
|
|
|
if (playerData.streamInfo && playSessionId) {
|
|
|
|
apiClient.stopActiveEncodings(playSessionId).then(function () {
|
|
|
|
// Stop the first transcoding afterwards because the player may still send requests to the original url
|
2020-07-31 09:12:30 +01:00
|
|
|
const afterSetSrc = function () {
|
2019-01-10 15:39:37 +03:00
|
|
|
apiClient.stopActiveEncodings(playSessionId);
|
|
|
|
};
|
|
|
|
setSrcIntoPlayer(apiClient, player, streamInfo).then(afterSetSrc, afterSetSrc);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
setSrcIntoPlayer(apiClient, player, streamInfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setSrcIntoPlayer(apiClient, player, streamInfo) {
|
|
|
|
return player.play(streamInfo).then(function () {
|
2020-07-31 09:12:30 +01:00
|
|
|
const playerData = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
playerData.isChangingStream = false;
|
|
|
|
playerData.streamInfo = streamInfo;
|
|
|
|
streamInfo.started = true;
|
|
|
|
streamInfo.ended = false;
|
|
|
|
|
|
|
|
sendProgressUpdate(player, 'timeupdate');
|
|
|
|
}, function (e) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const playerData = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
playerData.isChangingStream = false;
|
|
|
|
|
|
|
|
onPlaybackError.call(player, e, {
|
|
|
|
type: 'mediadecodeerror',
|
|
|
|
streamInfo: streamInfo
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function translateItemsForPlayback(items, options) {
|
2019-03-11 08:30:36 +01:00
|
|
|
if (items.length > 1 && options && options.ids) {
|
2019-03-11 08:24:26 +01:00
|
|
|
// Use the original request id array for sorting the result in the proper order
|
|
|
|
items.sort(function (a, b) {
|
|
|
|
return options.ids.indexOf(a.Id) - options.ids.indexOf(b.Id);
|
|
|
|
});
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const firstItem = items[0];
|
|
|
|
let promise;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const serverId = firstItem.ServerId;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const queryOptions = options.queryOptions || {};
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-05-04 12:44:12 +02:00
|
|
|
if (firstItem.Type === 'Program') {
|
2019-01-10 15:39:37 +03:00
|
|
|
promise = getItemsForPlayback(serverId, {
|
2019-11-23 00:29:38 +09:00
|
|
|
Ids: firstItem.ChannelId
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
2020-05-04 12:44:12 +02:00
|
|
|
} else if (firstItem.Type === 'Playlist') {
|
2019-01-10 15:39:37 +03:00
|
|
|
promise = getItemsForPlayback(serverId, {
|
|
|
|
ParentId: firstItem.Id,
|
|
|
|
SortBy: options.shuffle ? 'Random' : null
|
|
|
|
});
|
2020-05-04 12:44:12 +02:00
|
|
|
} else if (firstItem.Type === 'MusicArtist') {
|
2019-01-10 15:39:37 +03:00
|
|
|
promise = getItemsForPlayback(serverId, {
|
|
|
|
ArtistIds: firstItem.Id,
|
2020-05-04 12:44:12 +02:00
|
|
|
Filters: 'IsNotFolder',
|
2019-01-10 15:39:37 +03:00
|
|
|
Recursive: true,
|
|
|
|
SortBy: options.shuffle ? 'Random' : 'SortName',
|
2020-05-04 12:44:12 +02:00
|
|
|
MediaTypes: 'Audio'
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
2020-05-04 12:44:12 +02:00
|
|
|
} else if (firstItem.MediaType === 'Photo') {
|
2019-01-10 15:39:37 +03:00
|
|
|
promise = getItemsForPlayback(serverId, {
|
|
|
|
ParentId: firstItem.ParentId,
|
2020-05-04 12:44:12 +02:00
|
|
|
Filters: 'IsNotFolder',
|
2019-01-10 15:39:37 +03:00
|
|
|
// Setting this to true may cause some incorrect sorting
|
|
|
|
Recursive: false,
|
|
|
|
SortBy: options.shuffle ? 'Random' : 'SortName',
|
2020-06-05 16:04:18 +02:00
|
|
|
MediaTypes: 'Photo,Video'
|
2019-01-10 15:39:37 +03:00
|
|
|
}).then(function (result) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const items = result.Items;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let index = items.map(function (i) {
|
2019-01-10 15:39:37 +03:00
|
|
|
return i.Id;
|
|
|
|
}).indexOf(firstItem.Id);
|
|
|
|
|
|
|
|
if (index === -1) {
|
|
|
|
index = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
options.startIndex = index;
|
|
|
|
|
|
|
|
return Promise.resolve(result);
|
|
|
|
});
|
2020-05-04 12:44:12 +02:00
|
|
|
} else if (firstItem.Type === 'PhotoAlbum') {
|
2019-01-10 15:39:37 +03:00
|
|
|
promise = getItemsForPlayback(serverId, {
|
|
|
|
ParentId: firstItem.Id,
|
2020-05-04 12:44:12 +02:00
|
|
|
Filters: 'IsNotFolder',
|
2019-01-10 15:39:37 +03:00
|
|
|
// Setting this to true may cause some incorrect sorting
|
|
|
|
Recursive: false,
|
|
|
|
SortBy: options.shuffle ? 'Random' : 'SortName',
|
2020-05-04 12:44:12 +02:00
|
|
|
MediaTypes: 'Photo,Video',
|
2019-01-10 15:39:37 +03:00
|
|
|
Limit: 1000
|
|
|
|
});
|
2020-05-04 12:44:12 +02:00
|
|
|
} else if (firstItem.Type === 'MusicGenre') {
|
2019-01-10 15:39:37 +03:00
|
|
|
promise = getItemsForPlayback(serverId, {
|
|
|
|
GenreIds: firstItem.Id,
|
2020-05-04 12:44:12 +02:00
|
|
|
Filters: 'IsNotFolder',
|
2019-01-10 15:39:37 +03:00
|
|
|
Recursive: true,
|
|
|
|
SortBy: options.shuffle ? 'Random' : 'SortName',
|
2020-05-04 12:44:12 +02:00
|
|
|
MediaTypes: 'Audio'
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
2019-11-23 00:29:38 +09:00
|
|
|
} else if (firstItem.IsFolder) {
|
2019-01-10 15:39:37 +03:00
|
|
|
promise = getItemsForPlayback(serverId, mergePlaybackQueries({
|
|
|
|
ParentId: firstItem.Id,
|
2020-05-04 12:44:12 +02:00
|
|
|
Filters: 'IsNotFolder',
|
2019-01-10 15:39:37 +03:00
|
|
|
Recursive: true,
|
|
|
|
// These are pre-sorted
|
|
|
|
SortBy: options.shuffle ? 'Random' : (['BoxSet'].indexOf(firstItem.Type) === -1 ? 'SortName' : null),
|
2020-05-04 12:44:12 +02:00
|
|
|
MediaTypes: 'Audio,Video'
|
2019-01-10 15:39:37 +03:00
|
|
|
}, queryOptions));
|
2020-05-04 12:44:12 +02:00
|
|
|
} else if (firstItem.Type === 'Episode' && items.length === 1 && getPlayer(firstItem, options).supportsProgress !== false) {
|
2019-01-10 15:39:37 +03:00
|
|
|
promise = new Promise(function (resolve, reject) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = connectionManager.getApiClient(firstItem.ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
apiClient.getCurrentUser().then(function (user) {
|
|
|
|
if (!user.Configuration.EnableNextEpisodeAutoPlay || !firstItem.SeriesId) {
|
|
|
|
resolve(null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
apiClient.getEpisodes(firstItem.SeriesId, {
|
|
|
|
IsVirtualUnaired: false,
|
|
|
|
IsMissing: false,
|
|
|
|
UserId: apiClient.getCurrentUserId(),
|
2020-05-04 12:44:12 +02:00
|
|
|
Fields: 'Chapters'
|
2019-01-10 15:39:37 +03:00
|
|
|
}).then(function (episodesResult) {
|
2020-07-31 09:12:30 +01:00
|
|
|
let foundItem = false;
|
2019-01-10 15:39:37 +03:00
|
|
|
episodesResult.Items = episodesResult.Items.filter(function (e) {
|
|
|
|
if (foundItem) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (e.Id === firstItem.Id) {
|
|
|
|
foundItem = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
episodesResult.TotalRecordCount = episodesResult.Items.length;
|
|
|
|
resolve(episodesResult);
|
|
|
|
}, reject);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (promise) {
|
|
|
|
return promise.then(function (result) {
|
|
|
|
return result ? result.Items : items;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return Promise.resolve(items);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.play = function (options) {
|
|
|
|
normalizePlayOptions(options);
|
|
|
|
|
|
|
|
if (self._currentPlayer) {
|
|
|
|
if (options.enableRemotePlayers === false && !self._currentPlayer.isLocalPlayer) {
|
|
|
|
return Promise.reject();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!self._currentPlayer.isLocalPlayer) {
|
|
|
|
return self._currentPlayer.play(options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.fullscreen) {
|
|
|
|
loading.show();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.items) {
|
|
|
|
return translateItemsForPlayback(options.items, options).then(function (items) {
|
|
|
|
return playWithIntros(items, options);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
if (!options.serverId) {
|
|
|
|
throw new Error('serverId required!');
|
|
|
|
}
|
|
|
|
|
|
|
|
return getItemsForPlayback(options.serverId, {
|
|
|
|
Ids: options.ids.join(',')
|
|
|
|
}).then(function (result) {
|
|
|
|
return translateItemsForPlayback(result.Items, options).then(function (items) {
|
|
|
|
return playWithIntros(items, options);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function getPlayerData(player) {
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
|
|
|
}
|
|
|
|
if (!player.name) {
|
|
|
|
throw new Error('player name cannot be null');
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
let state = playerStates[player.name];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (!state) {
|
|
|
|
playerStates[player.name] = {};
|
|
|
|
state = playerStates[player.name];
|
|
|
|
}
|
|
|
|
|
|
|
|
return player;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.getPlayerState = function (player, item, mediaSource) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!enableLocalPlaylistManagement(player) && player.getPlayerState) {
|
|
|
|
return player.getPlayerState();
|
|
|
|
}
|
|
|
|
|
|
|
|
item = item || self.currentItem(player);
|
|
|
|
mediaSource = mediaSource || self.currentMediaSource(player);
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const state = {
|
2019-01-10 15:39:37 +03:00
|
|
|
PlayState: {}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (player) {
|
|
|
|
state.PlayState.VolumeLevel = player.getVolume();
|
|
|
|
state.PlayState.IsMuted = player.isMuted();
|
|
|
|
state.PlayState.IsPaused = player.paused();
|
|
|
|
state.PlayState.RepeatMode = self.getRepeatMode(player);
|
2020-06-22 11:25:16 +02:00
|
|
|
state.PlayState.ShuffleMode = self.getQueueShuffleMode(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
state.PlayState.MaxStreamingBitrate = self.getMaxStreamingBitrate(player);
|
|
|
|
|
|
|
|
state.PlayState.PositionTicks = getCurrentTicks(player);
|
|
|
|
state.PlayState.PlaybackStartTimeTicks = self.playbackStartTime(player);
|
|
|
|
|
|
|
|
state.PlayState.SubtitleStreamIndex = self.getSubtitleStreamIndex(player);
|
|
|
|
state.PlayState.AudioStreamIndex = self.getAudioStreamIndex(player);
|
|
|
|
state.PlayState.BufferedRanges = self.getBufferedRanges(player);
|
|
|
|
|
|
|
|
state.PlayState.PlayMethod = self.playMethod(player);
|
|
|
|
|
|
|
|
if (mediaSource) {
|
|
|
|
state.PlayState.LiveStreamId = mediaSource.LiveStreamId;
|
|
|
|
}
|
|
|
|
state.PlayState.PlaySessionId = self.playSessionId(player);
|
|
|
|
state.PlayState.PlaylistItemId = self.getCurrentPlaylistItemId(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mediaSource) {
|
|
|
|
state.PlayState.MediaSourceId = mediaSource.Id;
|
|
|
|
|
|
|
|
state.NowPlayingItem = {
|
|
|
|
RunTimeTicks: mediaSource.RunTimeTicks
|
|
|
|
};
|
|
|
|
|
|
|
|
state.PlayState.CanSeek = (mediaSource.RunTimeTicks || 0) > 0 || canPlayerSeek(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item) {
|
|
|
|
state.NowPlayingItem = getNowPlayingItemForReporting(player, item, mediaSource);
|
|
|
|
}
|
|
|
|
|
|
|
|
state.MediaSource = mediaSource;
|
|
|
|
|
|
|
|
return state;
|
|
|
|
};
|
|
|
|
|
|
|
|
self.duration = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (player && !enableLocalPlaylistManagement(player) && !player.isLocalPlayer) {
|
|
|
|
return player.duration();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const mediaSource = self.currentMediaSource(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (mediaSource && mediaSource.RunTimeTicks) {
|
|
|
|
return mediaSource.RunTimeTicks;
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let playerDuration = player.duration();
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (playerDuration) {
|
|
|
|
playerDuration *= 10000;
|
|
|
|
}
|
|
|
|
|
|
|
|
return playerDuration;
|
|
|
|
};
|
|
|
|
|
|
|
|
function getCurrentTicks(player) {
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
|
|
|
}
|
|
|
|
|
2020-07-31 10:36:08 +01:00
|
|
|
let playerTime = Math.floor(10000 * (player).currentTime());
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const streamInfo = getPlayerData(player).streamInfo;
|
2019-01-10 15:39:37 +03:00
|
|
|
if (streamInfo) {
|
|
|
|
playerTime += getPlayerData(player).streamInfo.transcodingOffsetTicks || 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return playerTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only used internally
|
|
|
|
self.getCurrentTicks = getCurrentTicks;
|
|
|
|
|
2020-05-05 23:02:05 +09:00
|
|
|
function playOther(items, options, user) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const playStartIndex = options.startIndex || 0;
|
|
|
|
const player = getPlayer(items[playStartIndex], options);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
loading.hide();
|
|
|
|
|
|
|
|
options.items = items;
|
|
|
|
|
|
|
|
return player.play(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
function playWithIntros(items, options, user) {
|
2020-07-31 09:12:30 +01:00
|
|
|
let playStartIndex = options.startIndex || 0;
|
|
|
|
let firstItem = items[playStartIndex];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
// If index was bad, reset it
|
|
|
|
if (!firstItem) {
|
|
|
|
playStartIndex = 0;
|
|
|
|
firstItem = items[playStartIndex];
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it's still null then there's nothing to play
|
|
|
|
if (!firstItem) {
|
|
|
|
showPlaybackInfoErrorMessage(self, 'NoCompatibleStream', false);
|
|
|
|
return Promise.reject();
|
|
|
|
}
|
|
|
|
|
2020-05-05 23:02:05 +09:00
|
|
|
if (firstItem.MediaType === 'Photo' || firstItem.MediaType === 'Book') {
|
|
|
|
return playOther(items, options, user);
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = connectionManager.getApiClient(firstItem.ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
return getIntros(firstItem, apiClient, options).then(function (introsResult) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const introItems = introsResult.Items;
|
|
|
|
let introPlayOptions;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
firstItem.playOptions = truncatePlayOptions(options);
|
|
|
|
|
|
|
|
if (introItems.length) {
|
|
|
|
introPlayOptions = {
|
|
|
|
fullscreen: firstItem.playOptions.fullscreen
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
introPlayOptions = firstItem.playOptions;
|
|
|
|
}
|
|
|
|
|
|
|
|
items = introItems.concat(items);
|
|
|
|
|
|
|
|
// Needed by players that manage their own playlist
|
|
|
|
introPlayOptions.items = items;
|
|
|
|
introPlayOptions.startIndex = playStartIndex;
|
|
|
|
|
|
|
|
return playInternal(items[playStartIndex], introPlayOptions, function () {
|
|
|
|
self._playQueueManager.setPlaylist(items);
|
|
|
|
|
|
|
|
setPlaylistState(items[playStartIndex].PlaylistItemId, playStartIndex);
|
|
|
|
loading.hide();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set playlist state. Using a method allows for overloading in derived player implementations
|
|
|
|
function setPlaylistState(playlistItemId, index) {
|
|
|
|
if (!isNaN(index)) {
|
|
|
|
self._playQueueManager.setPlaylistState(playlistItemId, index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function playInternal(item, playOptions, onPlaybackStartedFn) {
|
|
|
|
if (item.IsPlaceHolder) {
|
|
|
|
loading.hide();
|
|
|
|
showPlaybackInfoErrorMessage(self, 'PlaceHolder', true);
|
|
|
|
return Promise.reject();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normalize defaults to simplfy checks throughout the process
|
|
|
|
normalizePlayOptions(playOptions);
|
|
|
|
|
|
|
|
if (playOptions.isFirstItem) {
|
|
|
|
playOptions.isFirstItem = false;
|
|
|
|
} else {
|
|
|
|
playOptions.isFirstItem = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return runInterceptors(item, playOptions).then(function () {
|
|
|
|
if (playOptions.fullscreen) {
|
|
|
|
loading.show();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: This should be the media type requested, not the original media type
|
2020-07-31 09:12:30 +01:00
|
|
|
const mediaType = item.MediaType;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const onBitrateDetectionFailure = function () {
|
2019-01-10 15:39:37 +03:00
|
|
|
return playAfterBitrateDetect(getSavedMaxStreamingBitrate(connectionManager.getApiClient(item.ServerId), mediaType), item, playOptions, onPlaybackStartedFn);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!isServerItem(item) || itemHelper.isLocalItem(item)) {
|
|
|
|
return onBitrateDetectionFailure();
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = connectionManager.getApiClient(item.ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
apiClient.getEndpointInfo().then(function (endpointInfo) {
|
|
|
|
if ((mediaType === 'Video' || mediaType === 'Audio') && appSettings.enableAutomaticBitrateDetection(endpointInfo.IsInNetwork, mediaType)) {
|
|
|
|
return apiClient.detectBitrate().then(function (bitrate) {
|
|
|
|
appSettings.maxStreamingBitrate(endpointInfo.IsInNetwork, mediaType, bitrate);
|
|
|
|
|
|
|
|
return playAfterBitrateDetect(bitrate, item, playOptions, onPlaybackStartedFn);
|
|
|
|
}, onBitrateDetectionFailure);
|
|
|
|
} else {
|
|
|
|
onBitrateDetectionFailure();
|
|
|
|
}
|
|
|
|
}, onBitrateDetectionFailure);
|
|
|
|
}, onInterceptorRejection);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onInterceptorRejection() {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = self._currentPlayer;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (player) {
|
|
|
|
destroyPlayer(player);
|
|
|
|
removeCurrentPlayer(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
events.trigger(self, 'playbackcancelled');
|
|
|
|
|
|
|
|
return Promise.reject();
|
|
|
|
}
|
|
|
|
|
|
|
|
function destroyPlayer(player) {
|
|
|
|
player.destroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
function runInterceptors(item, playOptions) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const interceptors = pluginManager.ofType('preplayintercept');
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
interceptors.sort(function (a, b) {
|
|
|
|
return (a.order || 0) - (b.order || 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!interceptors.length) {
|
|
|
|
resolve();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
loading.hide();
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const options = Object.assign({}, playOptions);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
options.mediaType = item.MediaType;
|
|
|
|
options.item = item;
|
|
|
|
|
|
|
|
runNextPrePlay(interceptors, 0, options, resolve, reject);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function runNextPrePlay(interceptors, index, options, resolve, reject) {
|
|
|
|
if (index >= interceptors.length) {
|
|
|
|
resolve();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const interceptor = interceptors[index];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
interceptor.intercept(options).then(function () {
|
|
|
|
runNextPrePlay(interceptors, index + 1, options, resolve, reject);
|
|
|
|
}, reject);
|
|
|
|
}
|
|
|
|
|
|
|
|
function sendPlaybackListToPlayer(player, items, deviceProfile, maxBitrate, apiClient, startPositionTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, startIndex) {
|
|
|
|
return setStreamUrls(items, deviceProfile, maxBitrate, apiClient, startPositionTicks).then(function () {
|
|
|
|
loading.hide();
|
|
|
|
|
|
|
|
return player.play({
|
|
|
|
items: items,
|
|
|
|
startPositionTicks: startPositionTicks || 0,
|
|
|
|
mediaSourceId: mediaSourceId,
|
|
|
|
audioStreamIndex: audioStreamIndex,
|
|
|
|
subtitleStreamIndex: subtitleStreamIndex,
|
|
|
|
startIndex: startIndex
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function playAfterBitrateDetect(maxBitrate, item, playOptions, onPlaybackStartedFn) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const startPosition = playOptions.startPositionTicks;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = getPlayer(item, playOptions);
|
|
|
|
const activePlayer = self._currentPlayer;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let promise;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (activePlayer) {
|
|
|
|
// TODO: if changing players within the same playlist, this will cause nextItem to be null
|
|
|
|
self._playNextAfterEnded = false;
|
|
|
|
promise = onPlaybackChanging(activePlayer, player, item);
|
|
|
|
} else {
|
|
|
|
promise = Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2019-01-27 22:10:07 +01:00
|
|
|
if (!isServerItem(item) || item.MediaType === 'Book') {
|
2019-01-10 15:39:37 +03:00
|
|
|
return promise.then(function () {
|
2020-07-31 09:12:30 +01:00
|
|
|
const streamInfo = createStreamInfoFromUrlItem(item);
|
2019-01-10 15:39:37 +03:00
|
|
|
streamInfo.fullscreen = playOptions.fullscreen;
|
|
|
|
getPlayerData(player).isChangingStream = false;
|
|
|
|
return player.play(streamInfo).then(function () {
|
|
|
|
loading.hide();
|
|
|
|
onPlaybackStartedFn();
|
|
|
|
onPlaybackStarted(player, playOptions, streamInfo);
|
|
|
|
}, function () {
|
|
|
|
// TODO: show error message
|
|
|
|
self.stop(player);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.all([promise, player.getDeviceProfile(item)]).then(function (responses) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const deviceProfile = responses[1];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = connectionManager.getApiClient(item.ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const mediaSourceId = playOptions.mediaSourceId;
|
|
|
|
const audioStreamIndex = playOptions.audioStreamIndex;
|
|
|
|
const subtitleStreamIndex = playOptions.subtitleStreamIndex;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return sendPlaybackListToPlayer(player, playOptions.items, deviceProfile, maxBitrate, apiClient, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex, playOptions.startIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
// this reference was only needed by sendPlaybackListToPlayer
|
|
|
|
playOptions.items = null;
|
|
|
|
|
|
|
|
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(function (mediaSource) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
streamInfo.fullscreen = playOptions.fullscreen;
|
|
|
|
|
|
|
|
getPlayerData(player).isChangingStream = false;
|
|
|
|
getPlayerData(player).maxStreamingBitrate = maxBitrate;
|
|
|
|
|
|
|
|
return player.play(streamInfo).then(function () {
|
|
|
|
loading.hide();
|
|
|
|
onPlaybackStartedFn();
|
|
|
|
onPlaybackStarted(player, playOptions, streamInfo, mediaSource);
|
|
|
|
}, function (err) {
|
|
|
|
// TODO: Improve this because it will report playback start on a failure
|
|
|
|
onPlaybackStartedFn();
|
|
|
|
onPlaybackStarted(player, playOptions, streamInfo, mediaSource);
|
|
|
|
setTimeout(function () {
|
|
|
|
onPlaybackError.call(player, err, {
|
|
|
|
type: 'mediadecodeerror',
|
|
|
|
streamInfo: streamInfo
|
|
|
|
});
|
|
|
|
}, 100);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
self.getPlaybackInfo = function (item, options) {
|
|
|
|
options = options || {};
|
2020-07-31 09:12:30 +01:00
|
|
|
const startPosition = options.startPositionTicks || 0;
|
|
|
|
const mediaType = options.mediaType || item.MediaType;
|
|
|
|
const player = getPlayer(item, options);
|
|
|
|
const apiClient = connectionManager.getApiClient(item.ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
// Call this just to ensure the value is recorded, it is needed with getSavedMaxStreamingBitrate
|
|
|
|
return apiClient.getEndpointInfo().then(function () {
|
2020-07-31 09:12:30 +01:00
|
|
|
const maxBitrate = getSavedMaxStreamingBitrate(connectionManager.getApiClient(item.ServerId), mediaType);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
return player.getDeviceProfile(item).then(function (deviceProfile) {
|
|
|
|
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, options.mediaSourceId, options.audioStreamIndex, options.subtitleStreamIndex).then(function (mediaSource) {
|
|
|
|
return createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
self.getPlaybackMediaSources = function (item, options) {
|
|
|
|
options = options || {};
|
2020-07-31 09:12:30 +01:00
|
|
|
const startPosition = options.startPositionTicks || 0;
|
|
|
|
const mediaType = options.mediaType || item.MediaType;
|
2019-01-10 15:39:37 +03:00
|
|
|
// TODO: Remove the true forceLocalPlayer hack
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = getPlayer(item, options, true);
|
|
|
|
const apiClient = connectionManager.getApiClient(item.ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
// Call this just to ensure the value is recorded, it is needed with getSavedMaxStreamingBitrate
|
|
|
|
return apiClient.getEndpointInfo().then(function () {
|
2020-07-31 09:12:30 +01:00
|
|
|
const maxBitrate = getSavedMaxStreamingBitrate(connectionManager.getApiClient(item.ServerId), mediaType);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
return player.getDeviceProfile(item).then(function (deviceProfile) {
|
|
|
|
return getPlaybackInfo(player, apiClient, item, deviceProfile, maxBitrate, startPosition, false, null, null, null, null).then(function (playbackInfoResult) {
|
|
|
|
return playbackInfoResult.MediaSources;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
function createStreamInfo(apiClient, type, item, mediaSource, startPosition) {
|
2020-07-31 09:12:30 +01:00
|
|
|
let mediaUrl;
|
|
|
|
let contentType;
|
|
|
|
let transcodingOffsetTicks = 0;
|
|
|
|
const playerStartPositionTicks = startPosition;
|
|
|
|
const liveStreamId = mediaSource.LiveStreamId;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let playMethod = 'Transcode';
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const mediaSourceContainer = (mediaSource.Container || '').toLowerCase();
|
|
|
|
let directOptions;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (type === 'Video' || type === 'Audio') {
|
|
|
|
contentType = getMimeType(type.toLowerCase(), mediaSourceContainer);
|
|
|
|
|
|
|
|
if (mediaSource.enableDirectPlay) {
|
|
|
|
mediaUrl = mediaSource.Path;
|
|
|
|
|
|
|
|
playMethod = 'DirectPlay';
|
2019-11-23 00:29:38 +09:00
|
|
|
} else if (mediaSource.StreamUrl) {
|
2019-01-10 15:39:37 +03:00
|
|
|
// Only used for audio
|
|
|
|
playMethod = 'Transcode';
|
|
|
|
mediaUrl = mediaSource.StreamUrl;
|
2019-11-23 00:29:38 +09:00
|
|
|
} else if (mediaSource.SupportsDirectStream) {
|
2019-01-10 15:39:37 +03:00
|
|
|
directOptions = {
|
|
|
|
Static: true,
|
|
|
|
mediaSourceId: mediaSource.Id,
|
|
|
|
deviceId: apiClient.deviceId(),
|
|
|
|
api_key: apiClient.accessToken()
|
|
|
|
};
|
|
|
|
|
|
|
|
if (mediaSource.ETag) {
|
|
|
|
directOptions.Tag = mediaSource.ETag;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mediaSource.LiveStreamId) {
|
|
|
|
directOptions.LiveStreamId = mediaSource.LiveStreamId;
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const prefix = type === 'Video' ? 'Videos' : 'Audio';
|
2019-01-10 15:39:37 +03:00
|
|
|
mediaUrl = apiClient.getUrl(prefix + '/' + item.Id + '/stream.' + mediaSourceContainer, directOptions);
|
|
|
|
|
|
|
|
playMethod = 'DirectStream';
|
|
|
|
} else if (mediaSource.SupportsTranscoding) {
|
|
|
|
mediaUrl = apiClient.getUrl(mediaSource.TranscodingUrl);
|
|
|
|
|
|
|
|
if (mediaSource.TranscodingSubProtocol === 'hls') {
|
|
|
|
contentType = 'application/x-mpegURL';
|
|
|
|
} else {
|
|
|
|
contentType = getMimeType(type.toLowerCase(), mediaSource.TranscodingContainer);
|
|
|
|
|
|
|
|
if (mediaUrl.toLowerCase().indexOf('copytimestamps=true') === -1) {
|
|
|
|
transcodingOffsetTicks = startPosition || 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// All other media types
|
|
|
|
mediaUrl = mediaSource.Path;
|
|
|
|
playMethod = 'DirectPlay';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fallback (used for offline items)
|
|
|
|
if (!mediaUrl && mediaSource.SupportsDirectPlay) {
|
|
|
|
mediaUrl = mediaSource.Path;
|
|
|
|
playMethod = 'DirectPlay';
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const resultInfo = {
|
2019-01-10 15:39:37 +03:00
|
|
|
url: mediaUrl,
|
|
|
|
mimeType: contentType,
|
|
|
|
transcodingOffsetTicks: transcodingOffsetTicks,
|
|
|
|
playMethod: playMethod,
|
|
|
|
playerStartPositionTicks: playerStartPositionTicks,
|
|
|
|
item: item,
|
|
|
|
mediaSource: mediaSource,
|
|
|
|
textTracks: getTextTracks(apiClient, item, mediaSource),
|
|
|
|
// TODO: Deprecate
|
|
|
|
tracks: getTextTracks(apiClient, item, mediaSource),
|
|
|
|
mediaType: type,
|
|
|
|
liveStreamId: liveStreamId,
|
|
|
|
playSessionId: getParam('playSessionId', mediaUrl),
|
|
|
|
title: item.Name
|
|
|
|
};
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const backdropUrl = backdropImageUrl(apiClient, item, {});
|
2019-01-10 15:39:37 +03:00
|
|
|
if (backdropUrl) {
|
|
|
|
resultInfo.backdropUrl = backdropUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
return resultInfo;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getTextTracks(apiClient, item, mediaSource) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const subtitleStreams = mediaSource.MediaStreams.filter(function (s) {
|
2019-01-10 15:39:37 +03:00
|
|
|
return s.Type === 'Subtitle';
|
|
|
|
});
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const textStreams = subtitleStreams.filter(function (s) {
|
2019-01-10 15:39:37 +03:00
|
|
|
return s.DeliveryMethod === 'External';
|
|
|
|
});
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const tracks = [];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
for (let i = 0, length = textStreams.length; i < length; i++) {
|
|
|
|
const textStream = textStreams[i];
|
|
|
|
let textStreamUrl;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (itemHelper.isLocalItem(item)) {
|
|
|
|
textStreamUrl = textStream.Path;
|
|
|
|
} else {
|
|
|
|
textStreamUrl = !textStream.IsExternalUrl ? apiClient.getUrl(textStream.DeliveryUrl) : textStream.DeliveryUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
tracks.push({
|
|
|
|
url: textStreamUrl,
|
|
|
|
language: (textStream.Language || 'und'),
|
|
|
|
isDefault: textStream.Index === mediaSource.DefaultSubtitleStreamIndex,
|
|
|
|
index: textStream.Index,
|
|
|
|
format: textStream.Codec
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return tracks;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex) {
|
|
|
|
return getPlaybackInfo(player, apiClient, item, deviceProfile, maxBitrate, startPosition, true, mediaSourceId, audioStreamIndex, subtitleStreamIndex, null).then(function (playbackInfoResult) {
|
|
|
|
if (validatePlaybackInfoResult(self, playbackInfoResult)) {
|
|
|
|
return getOptimalMediaSource(apiClient, item, playbackInfoResult.MediaSources).then(function (mediaSource) {
|
|
|
|
if (mediaSource) {
|
|
|
|
if (mediaSource.RequiresOpening && !mediaSource.LiveStreamId) {
|
|
|
|
return getLiveStream(player, apiClient, item, playbackInfoResult.PlaySessionId, deviceProfile, maxBitrate, startPosition, mediaSource, null, null).then(function (openLiveStreamResult) {
|
|
|
|
return supportsDirectPlay(apiClient, item, openLiveStreamResult.MediaSource).then(function (result) {
|
|
|
|
openLiveStreamResult.MediaSource.enableDirectPlay = result;
|
|
|
|
return openLiveStreamResult.MediaSource;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return mediaSource;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
showPlaybackInfoErrorMessage(self, 'NoCompatibleStream');
|
|
|
|
return Promise.reject();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return Promise.reject();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPlayer(item, playOptions, forceLocalPlayers) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const serverItem = isServerItem(item);
|
2019-01-10 15:39:37 +03:00
|
|
|
return getAutomaticPlayers(self, forceLocalPlayers).filter(function (p) {
|
|
|
|
if (p.canPlayMediaType(item.MediaType)) {
|
|
|
|
if (serverItem) {
|
|
|
|
if (p.canPlayItem) {
|
|
|
|
return p.canPlayItem(item, playOptions);
|
|
|
|
}
|
|
|
|
return true;
|
2019-11-23 00:29:38 +09:00
|
|
|
} else if (item.Url && p.canPlayUrl) {
|
2019-01-10 15:39:37 +03:00
|
|
|
return p.canPlayUrl(item.Url);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
})[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
self.setCurrentPlaylistItem = function (playlistItemId, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.setCurrentPlaylistItem(playlistItemId);
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let newItem;
|
|
|
|
let newItemIndex;
|
|
|
|
const playlist = self._playQueueManager.getPlaylist();
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
for (let i = 0, length = playlist.length; i < length; i++) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (playlist[i].PlaylistItemId === playlistItemId) {
|
|
|
|
newItem = playlist[i];
|
|
|
|
newItemIndex = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newItem) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const newItemPlayOptions = newItem.playOptions || getDefaultPlayOptions();
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
playInternal(newItem, newItemPlayOptions, function () {
|
|
|
|
setPlaylistState(newItem.PlaylistItemId, newItemIndex);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.removeFromPlaylist = function (playlistItemIds, player) {
|
|
|
|
if (!playlistItemIds) {
|
|
|
|
throw new Error('Invalid playlistItemIds');
|
|
|
|
}
|
|
|
|
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.removeFromPlaylist(playlistItemIds);
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const removeResult = self._playQueueManager.removeFromPlaylist(playlistItemIds);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (removeResult.result === 'empty') {
|
|
|
|
return self.stop(player);
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const isCurrentIndex = removeResult.isCurrentIndex;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
events.trigger(player, 'playlistitemremove', [
|
|
|
|
{
|
|
|
|
playlistItemIds: playlistItemIds
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
|
|
|
]);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (isCurrentIndex) {
|
|
|
|
return self.setCurrentPlaylistItem(self._playQueueManager.getPlaylist()[0].PlaylistItemId, player);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.resolve();
|
|
|
|
};
|
|
|
|
|
|
|
|
self.movePlaylistItem = function (playlistItemId, newIndex, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.movePlaylistItem(playlistItemId, newIndex);
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const moveResult = self._playQueueManager.movePlaylistItem(playlistItemId, newIndex);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (moveResult.result === 'noop') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
events.trigger(player, 'playlistitemmove', [
|
|
|
|
{
|
|
|
|
playlistItemId: moveResult.playlistItemId,
|
|
|
|
newIndex: moveResult.newIndex
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
|
|
|
]);
|
2019-01-10 15:39:37 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
self.getCurrentPlaylistIndex = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.getCurrentPlaylistIndex();
|
|
|
|
}
|
|
|
|
|
|
|
|
return self._playQueueManager.getCurrentPlaylistIndex();
|
|
|
|
};
|
|
|
|
|
|
|
|
self.getCurrentPlaylistItemId = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.getCurrentPlaylistItemId();
|
|
|
|
}
|
|
|
|
|
|
|
|
return self._playQueueManager.getCurrentPlaylistItemId();
|
|
|
|
};
|
|
|
|
|
|
|
|
self.channelUp = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
return self.nextTrack(player);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.channelDown = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
return self.previousTrack(player);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.nextTrack = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.nextTrack();
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const newItemInfo = self._playQueueManager.getNextItemInfo();
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (newItemInfo) {
|
2020-02-16 03:44:43 +01:00
|
|
|
console.debug('playing next track');
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const newItemPlayOptions = newItemInfo.item.playOptions || getDefaultPlayOptions();
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
playInternal(newItemInfo.item, newItemPlayOptions, function () {
|
|
|
|
setPlaylistState(newItemInfo.item.PlaylistItemId, newItemInfo.index);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.previousTrack = function (player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.previousTrack();
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const newIndex = self.getCurrentPlaylistIndex(player) - 1;
|
2019-01-10 15:39:37 +03:00
|
|
|
if (newIndex >= 0) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const playlist = self._playQueueManager.getPlaylist();
|
|
|
|
const newItem = playlist[newIndex];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (newItem) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const newItemPlayOptions = newItem.playOptions || getDefaultPlayOptions();
|
2019-01-10 15:39:37 +03:00
|
|
|
newItemPlayOptions.startPositionTicks = 0;
|
|
|
|
|
|
|
|
playInternal(newItem, newItemPlayOptions, function () {
|
|
|
|
setPlaylistState(newItem.PlaylistItemId, newIndex);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-07-02 00:08:11 +02:00
|
|
|
self.queue = function (options, player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
queue(options, '', player);
|
|
|
|
};
|
|
|
|
|
2020-07-02 00:08:11 +02:00
|
|
|
self.queueNext = function (options, player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
queue(options, 'next', player);
|
|
|
|
};
|
|
|
|
|
|
|
|
function queue(options, mode, player) {
|
|
|
|
player = player || self._currentPlayer;
|
|
|
|
|
|
|
|
if (!player) {
|
|
|
|
return self.play(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.items) {
|
|
|
|
return translateItemsForPlayback(options.items, options).then(function (items) {
|
|
|
|
// TODO: Handle options.startIndex for photos
|
|
|
|
queueAll(items, mode, player);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
if (!options.serverId) {
|
|
|
|
throw new Error('serverId required!');
|
|
|
|
}
|
|
|
|
|
|
|
|
return getItemsForPlayback(options.serverId, {
|
|
|
|
Ids: options.ids.join(',')
|
|
|
|
}).then(function (result) {
|
|
|
|
return translateItemsForPlayback(result.Items, options).then(function (items) {
|
|
|
|
// TODO: Handle options.startIndex for photos
|
|
|
|
queueAll(items, mode, player);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function queueAll(items, mode, player) {
|
|
|
|
if (!items.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!player.isLocalPlayer) {
|
|
|
|
if (mode === 'next') {
|
|
|
|
player.queueNext({
|
|
|
|
items: items
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
player.queue({
|
|
|
|
items: items
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const queueDirectToPlayer = player && !enableLocalPlaylistManagement(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (queueDirectToPlayer) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = connectionManager.getApiClient(items[0].ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
player.getDeviceProfile(items[0]).then(function (profile) {
|
|
|
|
setStreamUrls(items, profile, self.getMaxStreamingBitrate(player), apiClient, 0).then(function () {
|
|
|
|
if (mode === 'next') {
|
|
|
|
player.queueNext(items);
|
|
|
|
} else {
|
|
|
|
player.queue(items);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode === 'next') {
|
|
|
|
self._playQueueManager.queueNext(items);
|
|
|
|
} else {
|
|
|
|
self._playQueueManager.queue(items);
|
|
|
|
}
|
2020-07-02 00:08:11 +02:00
|
|
|
events.trigger(player, 'playlistitemadd');
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
function onPlayerProgressInterval() {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
sendProgressUpdate(player, 'timeupdate');
|
|
|
|
}
|
|
|
|
|
|
|
|
function startPlaybackProgressTimer(player) {
|
|
|
|
stopPlaybackProgressTimer(player);
|
|
|
|
|
|
|
|
player._progressInterval = setInterval(onPlayerProgressInterval.bind(player), 10000);
|
|
|
|
}
|
|
|
|
|
|
|
|
function stopPlaybackProgressTimer(player) {
|
|
|
|
if (player._progressInterval) {
|
|
|
|
clearInterval(player._progressInterval);
|
|
|
|
player._progressInterval = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaybackStarted(player, playOptions, streamInfo, mediaSource) {
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
|
|
|
}
|
|
|
|
|
|
|
|
setCurrentPlayerInternal(player);
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const playerData = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
playerData.streamInfo = streamInfo;
|
|
|
|
|
|
|
|
streamInfo.playbackStartTimeTicks = new Date().getTime() * 10000;
|
|
|
|
|
|
|
|
if (mediaSource) {
|
|
|
|
playerData.audioStreamIndex = mediaSource.DefaultAudioStreamIndex;
|
|
|
|
playerData.subtitleStreamIndex = mediaSource.DefaultSubtitleStreamIndex;
|
|
|
|
} else {
|
|
|
|
playerData.audioStreamIndex = null;
|
|
|
|
playerData.subtitleStreamIndex = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
self._playNextAfterEnded = true;
|
2020-07-31 09:12:30 +01:00
|
|
|
const isFirstItem = playOptions.isFirstItem;
|
|
|
|
const fullscreen = playOptions.fullscreen;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const state = self.getPlayerState(player, streamInfo.item, streamInfo.mediaSource);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
reportPlayback(self, state, player, true, state.NowPlayingItem.ServerId, 'reportPlaybackStart');
|
|
|
|
|
|
|
|
state.IsFirstItem = isFirstItem;
|
|
|
|
state.IsFullscreen = fullscreen;
|
|
|
|
events.trigger(player, 'playbackstart', [state]);
|
|
|
|
events.trigger(self, 'playbackstart', [player, state]);
|
|
|
|
|
|
|
|
// only used internally as a safeguard to avoid reporting other events to the server before playback start
|
|
|
|
streamInfo.started = true;
|
|
|
|
|
|
|
|
startPlaybackProgressTimer(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaybackStartedFromSelfManagingPlayer(e, item, mediaSource) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
setCurrentPlayerInternal(player);
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const playOptions = item.playOptions || getDefaultPlayOptions();
|
|
|
|
const isFirstItem = playOptions.isFirstItem;
|
|
|
|
const fullscreen = playOptions.fullscreen;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
playOptions.isFirstItem = false;
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const playerData = getPlayerData(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
playerData.streamInfo = {};
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const streamInfo = playerData.streamInfo;
|
2019-01-10 15:39:37 +03:00
|
|
|
streamInfo.playbackStartTimeTicks = new Date().getTime() * 10000;
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const state = self.getPlayerState(player, item, mediaSource);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
reportPlayback(self, state, player, true, state.NowPlayingItem.ServerId, 'reportPlaybackStart');
|
|
|
|
|
|
|
|
state.IsFirstItem = isFirstItem;
|
|
|
|
state.IsFullscreen = fullscreen;
|
|
|
|
events.trigger(player, 'playbackstart', [state]);
|
|
|
|
events.trigger(self, 'playbackstart', [player, state]);
|
|
|
|
|
|
|
|
// only used internally as a safeguard to avoid reporting other events to the server before playback start
|
|
|
|
streamInfo.started = true;
|
|
|
|
|
|
|
|
startPlaybackProgressTimer(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaybackStoppedFromSelfManagingPlayer(e, playerStopInfo) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
stopPlaybackProgressTimer(player);
|
2020-07-31 09:12:30 +01:00
|
|
|
const state = self.getPlayerState(player, playerStopInfo.item, playerStopInfo.mediaSource);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const nextItem = playerStopInfo.nextItem;
|
|
|
|
const nextMediaType = playerStopInfo.nextMediaType;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const playbackStopInfo = {
|
2019-01-10 15:39:37 +03:00
|
|
|
player: player,
|
|
|
|
state: state,
|
|
|
|
nextItem: (nextItem ? nextItem.item : null),
|
|
|
|
nextMediaType: nextMediaType
|
|
|
|
};
|
|
|
|
|
|
|
|
state.NextMediaType = nextMediaType;
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const streamInfo = getPlayerData(player).streamInfo;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
// only used internally as a safeguard to avoid reporting other events to the server after playback stopped
|
|
|
|
streamInfo.ended = true;
|
|
|
|
|
|
|
|
if (isServerItem(playerStopInfo.item)) {
|
|
|
|
state.PlayState.PositionTicks = (playerStopInfo.positionMs || 0) * 10000;
|
|
|
|
|
|
|
|
reportPlayback(self, state, player, true, playerStopInfo.item.ServerId, 'reportPlaybackStopped');
|
|
|
|
}
|
|
|
|
|
|
|
|
state.NextItem = playbackStopInfo.nextItem;
|
|
|
|
|
|
|
|
events.trigger(player, 'playbackstop', [state]);
|
|
|
|
events.trigger(self, 'playbackstop', [playbackStopInfo]);
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const nextItemPlayOptions = nextItem ? (nextItem.item.playOptions || getDefaultPlayOptions()) : getDefaultPlayOptions();
|
|
|
|
const newPlayer = nextItem ? getPlayer(nextItem.item, nextItemPlayOptions) : null;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (newPlayer !== player) {
|
|
|
|
destroyPlayer(player);
|
|
|
|
removeCurrentPlayer(player);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function enablePlaybackRetryWithTranscoding(streamInfo, errorType, currentlyPreventsVideoStreamCopy, currentlyPreventsAudioStreamCopy) {
|
|
|
|
// mediadecodeerror, medianotsupported, network, servererror
|
|
|
|
if (streamInfo.mediaSource.SupportsTranscoding && (!currentlyPreventsVideoStreamCopy || !currentlyPreventsAudioStreamCopy)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaybackError(e, error) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
error = error || {};
|
|
|
|
|
|
|
|
// network
|
|
|
|
// mediadecodeerror
|
|
|
|
// medianotsupported
|
2020-07-31 09:12:30 +01:00
|
|
|
const errorType = error.type;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-02-16 03:44:43 +01:00
|
|
|
console.debug('playbackmanager playback error type: ' + (errorType || ''));
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const streamInfo = error.streamInfo || getPlayerData(player).streamInfo;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (streamInfo) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const currentlyPreventsVideoStreamCopy = streamInfo.url.toLowerCase().indexOf('allowvideostreamcopy=false') !== -1;
|
|
|
|
const currentlyPreventsAudioStreamCopy = streamInfo.url.toLowerCase().indexOf('allowaudiostreamcopy=false') !== -1;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
// Auto switch to transcoding
|
|
|
|
if (enablePlaybackRetryWithTranscoding(streamInfo, errorType, currentlyPreventsVideoStreamCopy, currentlyPreventsAudioStreamCopy)) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const startTime = getCurrentTicks(player) || streamInfo.playerStartPositionTicks;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
changeStream(player, startTime, {
|
|
|
|
// force transcoding
|
|
|
|
EnableDirectPlay: false,
|
|
|
|
EnableDirectStream: false,
|
|
|
|
AllowVideoStreamCopy: false,
|
|
|
|
AllowAudioStreamCopy: currentlyPreventsAudioStreamCopy || currentlyPreventsVideoStreamCopy ? false : null
|
2020-04-06 22:03:09 +02:00
|
|
|
});
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const displayErrorCode = 'NoCompatibleStream';
|
2019-01-10 15:39:37 +03:00
|
|
|
onPlaybackStopped.call(player, e, displayErrorCode);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaybackStopped(e, displayErrorCode) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (getPlayerData(player).isChangingStream) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
stopPlaybackProgressTimer(player);
|
|
|
|
|
|
|
|
// User clicked stop or content ended
|
2020-07-31 09:12:30 +01:00
|
|
|
const state = self.getPlayerState(player);
|
|
|
|
const data = getPlayerData(player);
|
|
|
|
const streamInfo = data.streamInfo;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const nextItem = self._playNextAfterEnded ? self._playQueueManager.getNextItemInfo() : null;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const nextMediaType = (nextItem ? nextItem.item.MediaType : null);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const playbackStopInfo = {
|
2019-01-10 15:39:37 +03:00
|
|
|
player: player,
|
|
|
|
state: state,
|
|
|
|
nextItem: (nextItem ? nextItem.item : null),
|
|
|
|
nextMediaType: nextMediaType
|
|
|
|
};
|
|
|
|
|
|
|
|
state.NextMediaType = nextMediaType;
|
|
|
|
|
|
|
|
if (isServerItem(streamInfo.item)) {
|
|
|
|
if (player.supportsProgress === false && state.PlayState && !state.PlayState.PositionTicks) {
|
|
|
|
state.PlayState.PositionTicks = streamInfo.item.RunTimeTicks;
|
|
|
|
}
|
|
|
|
|
|
|
|
// only used internally as a safeguard to avoid reporting other events to the server after playback stopped
|
|
|
|
streamInfo.ended = true;
|
|
|
|
|
|
|
|
reportPlayback(self, state, player, true, streamInfo.item.ServerId, 'reportPlaybackStopped');
|
|
|
|
}
|
|
|
|
|
|
|
|
state.NextItem = playbackStopInfo.nextItem;
|
|
|
|
|
|
|
|
if (!nextItem) {
|
|
|
|
self._playQueueManager.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
events.trigger(player, 'playbackstop', [state]);
|
|
|
|
events.trigger(self, 'playbackstop', [playbackStopInfo]);
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const nextItemPlayOptions = nextItem ? (nextItem.item.playOptions || getDefaultPlayOptions()) : getDefaultPlayOptions();
|
|
|
|
const newPlayer = nextItem ? getPlayer(nextItem.item, nextItemPlayOptions) : null;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (newPlayer !== player) {
|
|
|
|
destroyPlayer(player);
|
|
|
|
removeCurrentPlayer(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (displayErrorCode && typeof (displayErrorCode) === 'string') {
|
|
|
|
showPlaybackInfoErrorMessage(self, displayErrorCode, nextItem);
|
2019-11-23 00:29:38 +09:00
|
|
|
} else if (nextItem) {
|
2019-01-10 15:39:37 +03:00
|
|
|
self.nextTrack();
|
2020-03-08 00:24:53 +03:00
|
|
|
} else {
|
|
|
|
// Nothing more to play - clear data
|
|
|
|
data.streamInfo = null;
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaybackChanging(activePlayer, newPlayer, newItem) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const state = self.getPlayerState(activePlayer);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const serverId = self.currentItem(activePlayer).ServerId;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
// User started playing something new while existing content is playing
|
2020-07-31 09:12:30 +01:00
|
|
|
let promise;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
stopPlaybackProgressTimer(activePlayer);
|
|
|
|
unbindStopped(activePlayer);
|
|
|
|
|
|
|
|
if (activePlayer === newPlayer) {
|
|
|
|
// If we're staying with the same player, stop it
|
|
|
|
promise = activePlayer.stop(false);
|
|
|
|
} else {
|
|
|
|
// If we're switching players, tear down the current one
|
|
|
|
promise = activePlayer.stop(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return promise.then(function () {
|
|
|
|
bindStopped(activePlayer);
|
|
|
|
|
|
|
|
if (enableLocalPlaylistManagement(activePlayer)) {
|
|
|
|
reportPlayback(self, state, activePlayer, true, serverId, 'reportPlaybackStopped');
|
|
|
|
}
|
|
|
|
|
|
|
|
events.trigger(self, 'playbackstop', [{
|
|
|
|
player: activePlayer,
|
|
|
|
state: state,
|
|
|
|
nextItem: newItem,
|
|
|
|
nextMediaType: newItem.MediaType
|
|
|
|
}]);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function bindStopped(player) {
|
|
|
|
if (enableLocalPlaylistManagement(player)) {
|
|
|
|
events.off(player, 'stopped', onPlaybackStopped);
|
|
|
|
events.on(player, 'stopped', onPlaybackStopped);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaybackTimeUpdate(e) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
sendProgressUpdate(player, 'timeupdate');
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaybackPause(e) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
sendProgressUpdate(player, 'pause');
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaybackUnpause(e) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
sendProgressUpdate(player, 'unpause');
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaybackVolumeChange(e) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2020-04-17 14:34:34 +03:00
|
|
|
sendProgressUpdate(player, 'volumechange');
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
function onRepeatModeChange(e) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
sendProgressUpdate(player, 'repeatmodechange');
|
|
|
|
}
|
|
|
|
|
2020-06-22 11:25:16 +02:00
|
|
|
function onShuffleQueueModeChange() {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2020-06-22 11:25:16 +02:00
|
|
|
sendProgressUpdate(player, 'shufflequeuemodechange');
|
2020-06-18 01:19:59 +02:00
|
|
|
}
|
|
|
|
|
2019-01-10 15:39:37 +03:00
|
|
|
function onPlaylistItemMove(e) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
sendProgressUpdate(player, 'playlistitemmove', true);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaylistItemRemove(e) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
sendProgressUpdate(player, 'playlistitemremove', true);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onPlaylistItemAdd(e) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
sendProgressUpdate(player, 'playlistitemadd', true);
|
|
|
|
}
|
|
|
|
|
|
|
|
function unbindStopped(player) {
|
|
|
|
events.off(player, 'stopped', onPlaybackStopped);
|
|
|
|
}
|
|
|
|
|
|
|
|
function initLegacyVolumeMethods(player) {
|
|
|
|
player.getVolume = function () {
|
|
|
|
return player.volume();
|
|
|
|
};
|
|
|
|
player.setVolume = function (val) {
|
|
|
|
return player.volume(val);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function initMediaPlayer(player) {
|
|
|
|
players.push(player);
|
|
|
|
players.sort(function (a, b) {
|
|
|
|
return (a.priority || 0) - (b.priority || 0);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (player.isLocalPlayer !== false) {
|
|
|
|
player.isLocalPlayer = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
player.currentState = {};
|
|
|
|
|
|
|
|
if (!player.getVolume || !player.setVolume) {
|
|
|
|
initLegacyVolumeMethods(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (enableLocalPlaylistManagement(player)) {
|
|
|
|
events.on(player, 'error', onPlaybackError);
|
|
|
|
events.on(player, 'timeupdate', onPlaybackTimeUpdate);
|
|
|
|
events.on(player, 'pause', onPlaybackPause);
|
|
|
|
events.on(player, 'unpause', onPlaybackUnpause);
|
|
|
|
events.on(player, 'volumechange', onPlaybackVolumeChange);
|
|
|
|
events.on(player, 'repeatmodechange', onRepeatModeChange);
|
2020-06-22 11:25:16 +02:00
|
|
|
events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange);
|
2019-01-10 15:39:37 +03:00
|
|
|
events.on(player, 'playlistitemmove', onPlaylistItemMove);
|
|
|
|
events.on(player, 'playlistitemremove', onPlaylistItemRemove);
|
|
|
|
events.on(player, 'playlistitemadd', onPlaylistItemAdd);
|
|
|
|
} else if (player.isLocalPlayer) {
|
|
|
|
events.on(player, 'itemstarted', onPlaybackStartedFromSelfManagingPlayer);
|
|
|
|
events.on(player, 'itemstopped', onPlaybackStoppedFromSelfManagingPlayer);
|
|
|
|
events.on(player, 'timeupdate', onPlaybackTimeUpdate);
|
|
|
|
events.on(player, 'pause', onPlaybackPause);
|
|
|
|
events.on(player, 'unpause', onPlaybackUnpause);
|
|
|
|
events.on(player, 'volumechange', onPlaybackVolumeChange);
|
|
|
|
events.on(player, 'repeatmodechange', onRepeatModeChange);
|
2020-06-22 11:25:16 +02:00
|
|
|
events.on(player, 'shufflequeuemodechange', onShuffleQueueModeChange);
|
2019-01-10 15:39:37 +03:00
|
|
|
events.on(player, 'playlistitemmove', onPlaylistItemMove);
|
|
|
|
events.on(player, 'playlistitemremove', onPlaylistItemRemove);
|
|
|
|
events.on(player, 'playlistitemadd', onPlaylistItemAdd);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (player.isLocalPlayer) {
|
|
|
|
bindToFullscreenChange(player);
|
|
|
|
}
|
|
|
|
bindStopped(player);
|
|
|
|
}
|
|
|
|
|
|
|
|
events.on(pluginManager, 'registered', function (e, plugin) {
|
|
|
|
if (plugin.type === 'mediaplayer') {
|
|
|
|
initMediaPlayer(plugin);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
pluginManager.ofType('mediaplayer').map(initMediaPlayer);
|
|
|
|
|
|
|
|
function sendProgressUpdate(player, progressEventName, reportPlaylist) {
|
|
|
|
if (!player) {
|
|
|
|
throw new Error('player cannot be null');
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const state = self.getPlayerState(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (state.NowPlayingItem) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const serverId = state.NowPlayingItem.ServerId;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const streamInfo = getPlayerData(player).streamInfo;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (streamInfo && streamInfo.started && !streamInfo.ended) {
|
|
|
|
reportPlayback(self, state, player, reportPlaylist, serverId, 'reportPlaybackProgress', progressEventName);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (streamInfo && streamInfo.liveStreamId) {
|
|
|
|
if (new Date().getTime() - (streamInfo.lastMediaInfoQuery || 0) >= 600000) {
|
|
|
|
getLiveStreamMediaInfo(player, streamInfo, self.currentMediaSource(player), streamInfo.liveStreamId, serverId);
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
function getLiveStreamMediaInfo(player, streamInfo, mediaSource, liveStreamId, serverId) {
|
2020-02-16 03:44:43 +01:00
|
|
|
console.debug('getLiveStreamMediaInfo');
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
streamInfo.lastMediaInfoQuery = new Date().getTime();
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = connectionManager.getApiClient(serverId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (!apiClient.isMinServerVersion('3.2.70.7')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
connectionManager.getApiClient(serverId).getLiveStreamMediaInfo(liveStreamId).then(function (info) {
|
|
|
|
mediaSource.MediaStreams = info.MediaStreams;
|
|
|
|
events.trigger(player, 'mediastreamschange');
|
|
|
|
}, function () {
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
self.onAppClose = function () {
|
2020-07-31 09:12:30 +01:00
|
|
|
const player = this._currentPlayer;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
// Try to report playback stopped before the app closes
|
|
|
|
if (player && this.isPlaying(player)) {
|
|
|
|
this._playNextAfterEnded = false;
|
|
|
|
onPlaybackStopped.call(player);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
self.playbackStartTime = function (player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player && !enableLocalPlaylistManagement(player) && !player.isLocalPlayer) {
|
|
|
|
return player.playbackStartTime();
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const streamInfo = getPlayerData(player).streamInfo;
|
2019-01-10 15:39:37 +03:00
|
|
|
return streamInfo ? streamInfo.playbackStartTimeTicks : null;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (apphost.supports('remotecontrol')) {
|
2020-07-31 09:12:30 +01:00
|
|
|
import('serverNotifications').then(({ default: serverNotifications }) => {
|
2019-01-10 15:39:37 +03:00
|
|
|
events.on(serverNotifications, 'ServerShuttingDown', self.setDefaultPlayerActive.bind(self));
|
|
|
|
events.on(serverNotifications, 'ServerRestarting', self.setDefaultPlayerActive.bind(self));
|
|
|
|
});
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
getCurrentPlayer() {
|
2019-01-10 15:39:37 +03:00
|
|
|
return this._currentPlayer;
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
currentTime(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player && !enableLocalPlaylistManagement(player) && !player.isLocalPlayer) {
|
|
|
|
return player.currentTime();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.getCurrentTicks(player);
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
nextItem(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.nextItem();
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const nextItem = this._playQueueManager.getNextItemInfo();
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (!nextItem || !nextItem.item) {
|
|
|
|
return Promise.reject();
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = connectionManager.getApiClient(nextItem.item.ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
return apiClient.getItem(apiClient.getCurrentUserId(), nextItem.item.Id);
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
canQueue(item) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (item.Type === 'MusicAlbum' || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') {
|
|
|
|
return this.canQueueMediaType('Audio');
|
|
|
|
}
|
|
|
|
return this.canQueueMediaType(item.MediaType);
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
canQueueMediaType(mediaType) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (this._currentPlayer) {
|
|
|
|
return this._currentPlayer.canPlayMediaType(mediaType);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
isMuted(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player) {
|
|
|
|
return player.isMuted();
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
setMute(mute, player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player) {
|
|
|
|
player.setMute(mute);
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
toggleMute(mute, player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player) {
|
|
|
|
if (player.toggleMute) {
|
|
|
|
player.toggleMute();
|
|
|
|
} else {
|
|
|
|
player.setMute(!player.isMuted());
|
|
|
|
}
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
toggleDisplayMirroring() {
|
2019-01-10 15:39:37 +03:00
|
|
|
this.enableDisplayMirroring(!this.enableDisplayMirroring());
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
enableDisplayMirroring(enabled) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (enabled != null) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const val = enabled ? '1' : '0';
|
2019-01-10 15:39:37 +03:00
|
|
|
appSettings.set('displaymirror', val);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (appSettings.get('displaymirror') || '') !== '0';
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
nextChapter(player = this._currentPlayer) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const item = this.currentItem(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const ticks = this.getCurrentTicks(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const nextChapter = (item.Chapters || []).filter(function (i) {
|
2019-01-10 15:39:37 +03:00
|
|
|
return i.StartPositionTicks > ticks;
|
|
|
|
})[0];
|
|
|
|
|
|
|
|
if (nextChapter) {
|
|
|
|
this.seek(nextChapter.StartPositionTicks, player);
|
|
|
|
} else {
|
|
|
|
this.nextTrack(player);
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
previousChapter(player = this._currentPlayer) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const item = this.currentItem(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
let ticks = this.getCurrentTicks(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
// Go back 10 seconds
|
|
|
|
ticks -= 100000000;
|
|
|
|
|
|
|
|
// If there's no previous track, then at least rewind to beginning
|
|
|
|
if (this.getCurrentPlaylistIndex(player) === 0) {
|
|
|
|
ticks = Math.max(ticks, 0);
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const previousChapters = (item.Chapters || []).filter(function (i) {
|
2019-01-10 15:39:37 +03:00
|
|
|
return i.StartPositionTicks <= ticks;
|
2018-10-23 01:05:09 +03:00
|
|
|
});
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (previousChapters.length) {
|
|
|
|
this.seek(previousChapters[previousChapters.length - 1].StartPositionTicks, player);
|
|
|
|
} else {
|
|
|
|
this.previousTrack(player);
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
fastForward(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player.fastForward != null) {
|
|
|
|
player.fastForward(userSettings.skipForwardLength());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Go back 15 seconds
|
2020-07-31 09:12:30 +01:00
|
|
|
const offsetTicks = userSettings.skipForwardLength() * 10000;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
this.seekRelative(offsetTicks, player);
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
rewind(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player.rewind != null) {
|
|
|
|
player.rewind(userSettings.skipBackLength());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Go back 15 seconds
|
2020-07-31 09:12:30 +01:00
|
|
|
const offsetTicks = 0 - (userSettings.skipBackLength() * 10000);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
this.seekRelative(offsetTicks, player);
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
seekPercent(percent, player = this._currentPlayer) {
|
2020-07-31 09:12:30 +01:00
|
|
|
let ticks = this.duration(player) || 0;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
percent /= 100;
|
|
|
|
ticks *= percent;
|
|
|
|
this.seek(parseInt(ticks), player);
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
seekMs(ms, player = this._currentPlayer) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const ticks = ms * 10000;
|
2020-07-21 00:50:14 +02:00
|
|
|
this.seek(ticks, player);
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2020-07-21 00:50:14 +02:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
playTrailers(item) {
|
|
|
|
const player = this._currentPlayer;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (player && player.playTrailers) {
|
|
|
|
return player.playTrailers(item);
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = connectionManager.getApiClient(item.ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const instance = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (item.LocalTrailerCount) {
|
|
|
|
return apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(function (result) {
|
|
|
|
return instance.play({
|
|
|
|
items: result
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} else {
|
2020-07-31 09:12:30 +01:00
|
|
|
const remoteTrailers = item.RemoteTrailers || [];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (!remoteTrailers.length) {
|
|
|
|
return Promise.reject();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.play({
|
|
|
|
items: remoteTrailers.map(function (t) {
|
|
|
|
return {
|
|
|
|
Name: t.Name || (item.Name + ' Trailer'),
|
|
|
|
Url: t.Url,
|
|
|
|
MediaType: 'Video',
|
|
|
|
Type: 'Trailer',
|
|
|
|
ServerId: apiClient.serverId()
|
|
|
|
};
|
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
getSubtitleUrl(textStream, serverId) {
|
|
|
|
const apiClient = connectionManager.getApiClient(serverId);
|
2020-07-30 19:42:30 +02:00
|
|
|
|
|
|
|
return !textStream.IsExternalUrl ? apiClient.getUrl(textStream.DeliveryUrl) : textStream.DeliveryUrl;
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-02 09:22:03 +01:00
|
|
|
stop(player) {
|
2019-01-10 15:39:37 +03:00
|
|
|
player = player || this._currentPlayer;
|
|
|
|
if (player) {
|
|
|
|
if (enableLocalPlaylistManagement(player)) {
|
|
|
|
this._playNextAfterEnded = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: remove second param
|
|
|
|
return player.stop(true, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.resolve();
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
getBufferedRanges(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player) {
|
|
|
|
if (player.getBufferedRanges) {
|
|
|
|
return player.getBufferedRanges();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return [];
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
playPause(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player) {
|
|
|
|
if (player.playPause) {
|
|
|
|
return player.playPause();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (player.paused()) {
|
|
|
|
return this.unpause(player);
|
|
|
|
} else {
|
|
|
|
return this.pause(player);
|
|
|
|
}
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
paused(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player) {
|
|
|
|
return player.paused();
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
pause(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player) {
|
|
|
|
player.pause();
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
unpause(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player) {
|
|
|
|
player.unpause();
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
setPlaybackRate(value, player = this._currentPlayer) {
|
2020-05-05 12:01:43 +02:00
|
|
|
if (player && player.setPlaybackRate) {
|
2020-04-01 17:53:14 +02:00
|
|
|
player.setPlaybackRate(value);
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2020-04-01 17:53:14 +02:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
getPlaybackRate(player = this._currentPlayer) {
|
2020-05-05 12:01:43 +02:00
|
|
|
if (player && player.getPlaybackRate) {
|
2020-04-01 19:27:38 +02:00
|
|
|
return player.getPlaybackRate();
|
2020-04-01 17:53:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2020-04-01 17:53:14 +02:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
instantMix(item, player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player && player.instantMix) {
|
|
|
|
return player.instantMix(item);
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const apiClient = connectionManager.getApiClient(item.ServerId);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const options = {};
|
2019-01-10 15:39:37 +03:00
|
|
|
options.UserId = apiClient.getCurrentUserId();
|
|
|
|
options.Limit = 200;
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const instance = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
apiClient.getInstantMixFromItem(item.Id, options).then(function (result) {
|
2018-10-23 01:05:09 +03:00
|
|
|
instance.play({
|
|
|
|
items: result.Items
|
2019-01-10 15:39:37 +03:00
|
|
|
});
|
|
|
|
});
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
shuffle(shuffleItem, player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player && player.shuffle) {
|
|
|
|
return player.shuffle(shuffleItem);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.play({ items: [shuffleItem], shuffle: true });
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
audioTracks(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player.audioTracks) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const result = player.audioTracks();
|
2019-01-10 15:39:37 +03:00
|
|
|
if (result) {
|
|
|
|
return result;
|
|
|
|
}
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const mediaSource = this.currentMediaSource(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const mediaStreams = (mediaSource || {}).MediaStreams || [];
|
2019-01-10 15:39:37 +03:00
|
|
|
return mediaStreams.filter(function (s) {
|
|
|
|
return s.Type === 'Audio';
|
|
|
|
});
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
subtitleTracks(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player.subtitleTracks) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const result = player.subtitleTracks();
|
2019-01-10 15:39:37 +03:00
|
|
|
if (result) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const mediaSource = this.currentMediaSource(player);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const mediaStreams = (mediaSource || {}).MediaStreams || [];
|
2019-01-10 15:39:37 +03:00
|
|
|
return mediaStreams.filter(function (s) {
|
|
|
|
return s.Type === 'Subtitle';
|
|
|
|
});
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
getSupportedCommands(player) {
|
2019-01-10 15:39:37 +03:00
|
|
|
player = player || this._currentPlayer || { isLocalPlayer: true };
|
|
|
|
|
|
|
|
if (player.isLocalPlayer) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const list = [
|
2020-05-04 12:44:12 +02:00
|
|
|
'GoHome',
|
|
|
|
'GoToSettings',
|
|
|
|
'VolumeUp',
|
|
|
|
'VolumeDown',
|
|
|
|
'Mute',
|
|
|
|
'Unmute',
|
|
|
|
'ToggleMute',
|
|
|
|
'SetVolume',
|
|
|
|
'SetAudioStreamIndex',
|
|
|
|
'SetSubtitleStreamIndex',
|
|
|
|
'SetMaxStreamingBitrate',
|
|
|
|
'DisplayContent',
|
|
|
|
'GoToSearch',
|
|
|
|
'DisplayMessage',
|
|
|
|
'SetRepeatMode',
|
2020-06-23 19:09:22 +02:00
|
|
|
'SetShuffleQueue',
|
2020-05-04 12:44:12 +02:00
|
|
|
'PlayMediaSource',
|
|
|
|
'PlayTrailers'
|
2019-01-10 15:39:37 +03:00
|
|
|
];
|
|
|
|
|
2020-07-16 22:19:09 +02:00
|
|
|
if (appHost.default.supports('fullscreenchange')) {
|
2019-01-10 15:39:37 +03:00
|
|
|
list.push('ToggleFullscreen');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (player.supports) {
|
|
|
|
if (player.supports('PictureInPicture')) {
|
|
|
|
list.push('PictureInPicture');
|
|
|
|
}
|
2020-01-10 11:31:03 -05:00
|
|
|
if (player.supports('AirPlay')) {
|
|
|
|
list.push('AirPlay');
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player.supports('SetBrightness')) {
|
|
|
|
list.push('SetBrightness');
|
|
|
|
}
|
|
|
|
if (player.supports('SetAspectRatio')) {
|
|
|
|
list.push('SetAspectRatio');
|
|
|
|
}
|
2020-04-01 17:53:14 +02:00
|
|
|
if (player.supports('PlaybackRate')) {
|
|
|
|
list.push('PlaybackRate');
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return list;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const info = this.getPlayerInfo();
|
2019-01-10 15:39:37 +03:00
|
|
|
return info ? info.supportedCommands : [];
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
setRepeatMode(value, player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.setRepeatMode(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._playQueueManager.setRepeatMode(value);
|
|
|
|
events.trigger(player, 'repeatmodechange');
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
getRepeatMode(player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.getRepeatMode();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this._playQueueManager.getRepeatMode();
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
setQueueShuffleMode(value, player = this._currentPlayer) {
|
2020-06-18 01:19:59 +02:00
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
2020-06-22 12:41:22 +02:00
|
|
|
return player.setQueueShuffleMode(value);
|
2020-06-18 01:19:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
this._playQueueManager.setShuffleMode(value);
|
2020-06-22 11:25:16 +02:00
|
|
|
events.trigger(player, 'shufflequeuemodechange');
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2020-06-18 01:19:59 +02:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
getQueueShuffleMode(player = this._currentPlayer) {
|
2020-06-18 01:19:59 +02:00
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
2020-06-22 11:25:16 +02:00
|
|
|
return player.getQueueShuffleMode();
|
2020-06-18 01:19:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return this._playQueueManager.getShuffleMode();
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2020-06-18 01:19:59 +02:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
toggleQueueShuffleMode(player = this._currentPlayer) {
|
2020-06-23 19:09:22 +02:00
|
|
|
let currentvalue;
|
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
currentvalue = player.getQueueShuffleMode();
|
|
|
|
switch (currentvalue) {
|
|
|
|
case 'Shuffle':
|
|
|
|
player.setQueueShuffleMode('Sorted');
|
|
|
|
break;
|
|
|
|
case 'Sorted':
|
|
|
|
player.setQueueShuffleMode('Shuffle');
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new TypeError('current value for shufflequeue is invalid');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this._playQueueManager.toggleShuffleMode();
|
|
|
|
}
|
|
|
|
events.trigger(player, 'shufflequeuemodechange');
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2020-06-23 19:09:22 +02:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
clearQueue(clearCurrentItem = false, player = this._currentPlayer) {
|
2020-07-01 15:54:51 +02:00
|
|
|
if (player && !enableLocalPlaylistManagement(player)) {
|
|
|
|
return player.clearQueue(clearCurrentItem);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._playQueueManager.clearPlaylist(clearCurrentItem);
|
|
|
|
events.trigger(player, 'playlistitemremove');
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2020-07-01 15:54:51 +02:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
trySetActiveDeviceName(name) {
|
2018-10-23 01:05:09 +03:00
|
|
|
name = normalizeName(name);
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
const instance = this;
|
2019-01-10 15:39:37 +03:00
|
|
|
instance.getTargets().then(function (result) {
|
2020-07-31 09:12:30 +01:00
|
|
|
const target = result.filter(function (p) {
|
2019-01-10 15:39:37 +03:00
|
|
|
return normalizeName(p.name) === name;
|
2018-10-23 01:05:09 +03:00
|
|
|
})[0];
|
2019-01-10 15:39:37 +03:00
|
|
|
|
|
|
|
if (target) {
|
|
|
|
instance.trySetActivePlayer(target.playerName, target);
|
|
|
|
}
|
|
|
|
});
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-08-01 21:03:27 +01:00
|
|
|
displayContent(options, player = this._currentPlayer) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player && player.displayContent) {
|
|
|
|
player.displayContent(options);
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
beginPlayerUpdates(player) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player.beginPlayerUpdates) {
|
|
|
|
player.beginPlayerUpdates();
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
endPlayerUpdates(player) {
|
2019-01-10 15:39:37 +03:00
|
|
|
if (player.endPlayerUpdates) {
|
|
|
|
player.endPlayerUpdates();
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
setDefaultPlayerActive() {
|
2019-01-10 15:39:37 +03:00
|
|
|
this.setActivePlayer('localplayer');
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
removeActivePlayer(name) {
|
|
|
|
const playerInfo = this.getPlayerInfo();
|
2019-01-10 15:39:37 +03:00
|
|
|
if (playerInfo) {
|
|
|
|
if (playerInfo.name === name) {
|
|
|
|
this.setDefaultPlayerActive();
|
|
|
|
}
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
removeActiveTarget(id) {
|
|
|
|
const playerInfo = this.getPlayerInfo();
|
2019-01-10 15:39:37 +03:00
|
|
|
if (playerInfo) {
|
|
|
|
if (playerInfo.id === id) {
|
|
|
|
this.setDefaultPlayerActive();
|
|
|
|
}
|
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
sendCommand(cmd, player) {
|
2020-02-16 03:44:43 +01:00
|
|
|
console.debug('MediaController received command: ' + cmd.Name);
|
2019-01-10 15:39:37 +03:00
|
|
|
switch (cmd.Name) {
|
|
|
|
case 'SetRepeatMode':
|
2018-10-23 01:05:09 +03:00
|
|
|
this.setRepeatMode(cmd.Arguments.RepeatMode, player);
|
|
|
|
break;
|
2020-06-23 19:09:22 +02:00
|
|
|
case 'SetShuffleQueue':
|
2020-06-22 11:25:16 +02:00
|
|
|
this.setQueueShuffleMode(cmd.Arguments.ShuffleMode, player);
|
2020-06-18 01:19:59 +02:00
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'VolumeUp':
|
2018-10-23 01:05:09 +03:00
|
|
|
this.volumeUp(player);
|
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'VolumeDown':
|
2018-10-23 01:05:09 +03:00
|
|
|
this.volumeDown(player);
|
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'Mute':
|
|
|
|
this.setMute(true, player);
|
2018-10-23 01:05:09 +03:00
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'Unmute':
|
|
|
|
this.setMute(false, player);
|
2018-10-23 01:05:09 +03:00
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'ToggleMute':
|
2018-10-23 01:05:09 +03:00
|
|
|
this.toggleMute(player);
|
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'SetVolume':
|
2018-10-23 01:05:09 +03:00
|
|
|
this.setVolume(cmd.Arguments.Volume, player);
|
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'SetAspectRatio':
|
2018-10-23 01:05:09 +03:00
|
|
|
this.setAspectRatio(cmd.Arguments.AspectRatio, player);
|
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'SetBrightness':
|
2018-10-23 01:05:09 +03:00
|
|
|
this.setBrightness(cmd.Arguments.Brightness, player);
|
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'SetAudioStreamIndex':
|
2018-10-23 01:05:09 +03:00
|
|
|
this.setAudioStreamIndex(parseInt(cmd.Arguments.Index), player);
|
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'SetSubtitleStreamIndex':
|
2018-10-23 01:05:09 +03:00
|
|
|
this.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index), player);
|
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'SetMaxStreamingBitrate':
|
2020-07-19 16:15:11 +02:00
|
|
|
this.setMaxStreamingBitrate(parseInt(cmd.Arguments.Bitrate), player);
|
2018-10-23 01:05:09 +03:00
|
|
|
break;
|
2019-01-10 15:39:37 +03:00
|
|
|
case 'ToggleFullscreen':
|
2018-10-23 01:05:09 +03:00
|
|
|
this.toggleFullscreen(player);
|
|
|
|
break;
|
|
|
|
default:
|
2019-03-22 13:25:00 -07:00
|
|
|
if (player.sendCommand) {
|
|
|
|
player.sendCommand(cmd);
|
2019-01-10 15:39:37 +03:00
|
|
|
}
|
2019-03-22 13:25:00 -07:00
|
|
|
break;
|
2018-10-23 01:05:09 +03:00
|
|
|
}
|
2020-07-31 09:12:30 +01:00
|
|
|
}
|
|
|
|
}
|
2019-01-10 15:39:37 +03:00
|
|
|
|
2020-07-31 09:12:30 +01:00
|
|
|
export default new PlaybackManager();
|