diff --git a/src/app.ts b/src/app.ts index 18b4dd16..add4403b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,18 +3,6 @@ import './components/maincontroller'; import './css/glyphicons.css'; import './css/jellyfin.css'; -const senders = cast.framework.CastReceiverContext.getInstance().getSenders(); -const id = - senders.length !== 0 && senders[0].id - ? senders[0].id - : new Date().getTime(); - -window.deviceInfo = { - deviceId: id, - deviceName: 'Google Cast', - versionNumber: RECEIVERVERSION -}; - window.mediaElement = document.getElementById('video-player'); window.playlist = []; diff --git a/src/components/jellyfinActions.ts b/src/components/jellyfinActions.ts index 0d408bba..cbce5eb4 100644 --- a/src/components/jellyfinActions.ts +++ b/src/components/jellyfinActions.ts @@ -1,7 +1,6 @@ import { getSenderReportingData, resetPlaybackScope, - extend, broadcastToMessageBus } from '../helpers'; @@ -199,12 +198,22 @@ export function pingTranscoder( */ export function load( $scope: GlobalScope, - customData: PlaybackProgressInfo, + customData: any, serverItem: BaseItemDto ): void { resetPlaybackScope($scope); - extend($scope, customData); + // These are set up in maincontroller.createMediaInformation + $scope.playSessionId = customData.playSessionId; + $scope.audioStreamIndex = customData.audioStreamIndex; + $scope.subtitleStreamIndex = customData.subtitleStreamIndex; + $scope.startPositionTicks = customData.startPositionTicks; + $scope.canSeek = customData.canSeek; + $scope.itemId = customData.itemId; + $scope.liveStreamId = customData.liveStreamId; + $scope.mediaSourceId = customData.mediaSourceId; + $scope.playMethod = customData.playMethod; + $scope.runtimeTicks = customData.runtimeTicks; $scope.item = serverItem; @@ -230,7 +239,7 @@ export function play($scope: GlobalScope): void { DocumentManager.getAppStatus() == 'audio' ) { setTimeout(() => { - window.mediaManager.play(); + window.playerManager.play(); if ($scope.mediaType == 'Audio') { DocumentManager.setAppStatus('audio'); @@ -409,7 +418,7 @@ export async function detectBitrate(): Promise { */ export function stopActiveEncodings($scope: GlobalScope): Promise { const options = { - deviceId: window.deviceInfo.deviceId, + deviceId: JellyfinApi.deviceId, PlaySessionId: undefined }; diff --git a/src/components/jellyfinApi.ts b/src/components/jellyfinApi.ts index 8221ea07..18957279 100644 --- a/src/components/jellyfinApi.ts +++ b/src/components/jellyfinApi.ts @@ -11,17 +11,46 @@ export abstract class JellyfinApi { // Address of server public static serverAddress: string | null = null; + // device name + public static deviceName = 'Google Cast'; + + // unique id + public static deviceId = ''; + + // version + public static versionNumber = RECEIVERVERSION; + public static setServerInfo( userId: string, accessToken: string, - serverAddress: string + serverAddress: string, + receiverName = '' ): void { console.debug( - `JellyfinApi.setServerInfo: user:${userId}, token:${accessToken}, server:${serverAddress}` + `JellyfinApi.setServerInfo: user:${userId}, token:${accessToken}, ` + + `server:${serverAddress}, name:${receiverName}` ); this.userId = userId; this.accessToken = accessToken; this.serverAddress = serverAddress; + + // remove special characters from the receiver name + receiverName = receiverName.replace(/[^\w\s]/gi, ''); + + if (receiverName) { + this.deviceName = receiverName; + // deviceId just needs to be unique-ish + this.deviceId = btoa(receiverName); + } else { + const senders = + cast.framework.CastReceiverContext.getInstance().getSenders(); + + this.deviceName = 'Google Cast'; + this.deviceId = + senders.length !== 0 && senders[0].id + ? senders[0].id + : new Date().getTime().toString(); + } } // create the necessary headers for authentication @@ -29,10 +58,8 @@ export abstract class JellyfinApi { // TODO throw error if this fails let auth = - `Emby Client="Chromecast", ` + - `Device="${window.deviceInfo.deviceName}", ` + - `DeviceId="${window.deviceInfo.deviceId}", ` + - `Version="${window.deviceInfo.versionNumber}"`; + `Jellyfin Client="Chromecast", Device="${this.deviceName}", ` + + `DeviceId="${this.deviceId}", Version="${this.versionNumber}"`; if (this.userId) { auth += `, UserId="${this.userId}"`; diff --git a/src/components/maincontroller.ts b/src/components/maincontroller.ts index 21f93a45..1afd2b89 100644 --- a/src/components/maincontroller.ts +++ b/src/components/maincontroller.ts @@ -8,10 +8,8 @@ import { getShuffleItems, getInstantMixItems, translateRequestedItems, - extend, broadcastToMessageBus, - broadcastConnectionErrorMessage, - cleanName + broadcastConnectionErrorMessage } from '../helpers'; import { reportPlaybackProgress, @@ -32,16 +30,14 @@ import { MediaSourceInfo } from '~/api/generated/models/media-source-info'; import { GlobalScope, PlayRequest } from '~/types/global'; window.castReceiverContext = cast.framework.CastReceiverContext.getInstance(); -window.mediaManager = window.castReceiverContext.getPlayerManager(); +window.playerManager = window.castReceiverContext.getPlayerManager(); -const playbackMgr = new playbackManager(window.mediaManager); +const playbackMgr = new playbackManager(window.playerManager); -CommandHandler.configure(window.mediaManager, playbackMgr); +CommandHandler.configure(window.playerManager, playbackMgr); resetPlaybackScope($scope); -const mgr = window.mediaManager; - let broadcastToServer = new Date(); let hasReportedCapabilities = false; @@ -106,7 +102,7 @@ function onMediaElementVolumeChange(event: framework.system.Event): void { * */ export function enableTimeUpdateListener(): void { - window.mediaManager.addEventListener( + window.playerManager.addEventListener( cast.framework.events.EventType.TIME_UPDATE, onMediaElementTimeUpdate ); @@ -114,11 +110,11 @@ export function enableTimeUpdateListener(): void { cast.framework.system.EventType.SYSTEM_VOLUME_CHANGED, onMediaElementVolumeChange ); - window.mediaManager.addEventListener( + window.playerManager.addEventListener( cast.framework.events.EventType.PAUSE, onMediaElementPause ); - window.mediaManager.addEventListener( + window.playerManager.addEventListener( cast.framework.events.EventType.PLAYING, onMediaElementPlaying ); @@ -128,7 +124,7 @@ export function enableTimeUpdateListener(): void { * */ export function disableTimeUpdateListener(): void { - window.mediaManager.removeEventListener( + window.playerManager.removeEventListener( cast.framework.events.EventType.TIME_UPDATE, onMediaElementTimeUpdate ); @@ -136,11 +132,11 @@ export function disableTimeUpdateListener(): void { cast.framework.system.EventType.SYSTEM_VOLUME_CHANGED, onMediaElementVolumeChange ); - window.mediaManager.removeEventListener( + window.playerManager.removeEventListener( cast.framework.events.EventType.PAUSE, onMediaElementPause ); - window.mediaManager.removeEventListener( + window.playerManager.removeEventListener( cast.framework.events.EventType.PLAYING, onMediaElementPlaying ); @@ -154,14 +150,20 @@ window.addEventListener('beforeunload', () => { reportPlaybackStopped($scope, getReportingParams($scope)); }); -mgr.addEventListener(cast.framework.events.EventType.PLAY, (): void => { - play($scope); - reportPlaybackProgress($scope, getReportingParams($scope)); -}); +window.playerManager.addEventListener( + cast.framework.events.EventType.PLAY, + (): void => { + play($scope); + reportPlaybackProgress($scope, getReportingParams($scope)); + } +); -mgr.addEventListener(cast.framework.events.EventType.PAUSE, (): void => { - reportPlaybackProgress($scope, getReportingParams($scope)); -}); +window.playerManager.addEventListener( + cast.framework.events.EventType.PAUSE, + (): void => { + reportPlaybackProgress($scope, getReportingParams($scope)); + } +); /** * @@ -170,34 +172,40 @@ function defaultOnStop(): void { playbackMgr.stop(); } -mgr.addEventListener( +window.playerManager.addEventListener( cast.framework.events.EventType.MEDIA_FINISHED, defaultOnStop ); -mgr.addEventListener(cast.framework.events.EventType.ABORT, defaultOnStop); +window.playerManager.addEventListener( + cast.framework.events.EventType.ABORT, + defaultOnStop +); -mgr.addEventListener(cast.framework.events.EventType.ENDED, () => { - // Ignore - if ($scope.isChangingStream) { - return; - } +window.playerManager.addEventListener( + cast.framework.events.EventType.ENDED, + (): void => { + // If we're changing streams, do not report playback ended. + if ($scope.isChangingStream) { + return; + } - reportPlaybackStopped($scope, getReportingParams($scope)); - resetPlaybackScope($scope); + reportPlaybackStopped($scope, getReportingParams($scope)); + resetPlaybackScope($scope); - if (!playbackMgr.playNextItem()) { - window.playlist = []; - window.currentPlaylistIndex = -1; - DocumentManager.startBackdropInterval(); + if (!playbackMgr.playNextItem()) { + window.playlist = []; + window.currentPlaylistIndex = -1; + DocumentManager.startBackdropInterval(); + } } -}); +); // Set the active subtitle track once the player has loaded -window.mediaManager.addEventListener( +window.playerManager.addEventListener( cast.framework.events.EventType.PLAYER_LOAD_COMPLETE, () => { setTextTrack( - window.mediaManager.getMediaInformation().customData + window.playerManager.getMediaInformation().customData .subtitleStreamIndex ); } @@ -251,38 +259,30 @@ export function processMessage(data: any): void { return; } + data.options = data.options || {}; + // Items will have properties - Id, Name, Type, MediaType, IsFolder JellyfinApi.setServerInfo( data.userId, data.accessToken, - data.serverAddress + data.serverAddress, + data.receiverName ); if (data.subtitleAppearance) { window.subtitleAppearance = data.subtitleAppearance; } + if (data.maxBitrate) { + window.MaxBitrate = data.maxBitrate; + } + // Report device capabilities if (!hasReportedCapabilities) { reportDeviceCapabilities(); } - data.options = data.options || {}; - - const cleanReceiverName = cleanName(data.receiverName || ''); - - window.deviceInfo.deviceName = - cleanReceiverName || window.deviceInfo.deviceName; - // deviceId just needs to be unique-ish - window.deviceInfo.deviceId = cleanReceiverName - ? btoa(cleanReceiverName) - : window.deviceInfo.deviceId; - - if (data.maxBitrate) { - window.MaxBitrate = data.maxBitrate; - } - CommandHandler.processMessage(data, data.command); if (window.reportEventType) { @@ -424,10 +424,10 @@ export async function changeStream( params: any = undefined ): Promise { if ( - window.mediaManager.getMediaInformation().customData.canClientSeek && + window.playerManager.getMediaInformation().customData.canClientSeek && params == null ) { - window.mediaManager.seek(ticks / 10000000); + window.playerManager.seek(ticks / 10000000); reportPlaybackProgress($scope, getReportingParams($scope)); return Promise.resolve(); @@ -492,12 +492,12 @@ export async function changeStream( const requiresStoppingTranscoding = false; if (requiresStoppingTranscoding) { - window.mediaManager.pause(); + window.playerManager.pause(); await stopActiveEncodings(playSessionId); } - window.mediaManager.load(loadRequest); - window.mediaManager.play(); + window.playerManager.load(loadRequest); + window.playerManager.play(); $scope.subtitleStreamIndex = subtitleStreamIndex; $scope.audioStreamIndex = audioStreamIndex; } @@ -590,8 +590,12 @@ export async function shuffle( } /** - * @param item - * @param options + * This function fetches the full information of an item before playing it. + * Only item.Id needs to be set. + * + * @param item - Item to look up + * @param options - Extra information about how it should be played back. + * @returns Promise waiting for the item to be loaded for playback */ export async function onStopPlayerBeforePlaybackDone( item: BaseItemDto, @@ -602,8 +606,6 @@ export async function onStopPlayerBeforePlaybackDone( type: 'GET' }); - // Attach the custom properties we created like userId, serverAddress, itemId, etc - extend(data, item); playbackMgr.playItemInternal(data, options); broadcastConnectionErrorMessage(); } @@ -714,7 +716,7 @@ export function checkDirectPlay(mediaSource: MediaSourceInfo): void { */ export function setTextTrack(index: number | null): void { try { - const textTracksManager = window.mediaManager.getTextTracksManager(); + const textTracksManager = window.playerManager.getTextTracksManager(); if (index == null) { // docs: null is okay @@ -806,6 +808,7 @@ export function createMediaInformation( mediaInfo.contentId = streamInfo.url; mediaInfo.contentType = streamInfo.contentType; + // TODO make a type for this mediaInfo.customData = { audioStreamIndex: streamInfo.audioStreamIndex, canClientSeek: streamInfo.canClientSeek, @@ -831,7 +834,9 @@ export function createMediaInformation( ); } - mediaInfo.customData.startPositionTicks = streamInfo.startPosition || 0; + // If the client actually sets startPosition: + // if(streamInfo.startPosition) + // mediaInfo.customData.startPositionTicks = streamInfo.startPosition return mediaInfo; } @@ -870,7 +875,7 @@ if (!PRODUCTION) { // quits once the client closes the connection. // options.maxInactivity = 3600; - window.mediaManager.addEventListener( + window.playerManager.addEventListener( cast.framework.events.category.CORE, (event: framework.events.Event) => { console.log(`Core event: ${event.type}`); diff --git a/src/helpers.ts b/src/helpers.ts index 962433bd..787b269e 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -16,8 +16,8 @@ import { GlobalScope, BusMessage, ItemIndex, ItemQuery } from './types/global'; * @returns position in ticks */ export function getCurrentPositionTicks($scope: GlobalScope): number { - let positionTicks = window.mediaManager.getCurrentTimeSec() * 10000000; - const mediaInformation = window.mediaManager.getMediaInformation(); + let positionTicks = window.playerManager.getCurrentTimeSec() * 10000000; + const mediaInformation = window.playerManager.getMediaInformation(); if (mediaInformation && !mediaInformation.customData.canClientSeek) { positionTicks += $scope.startPositionTicks || 0; @@ -43,7 +43,7 @@ export function getReportingParams($scope: GlobalScope): PlaybackProgressInfo { CanSeek: $scope.canSeek, IsMuted: window.volume.muted, IsPaused: - window.mediaManager.getPlayerState() === + window.playerManager.getPlayerState() === cast.framework.messages.PlayerState.PAUSED, ItemId: $scope.itemId, LiveStreamId: $scope.liveStreamId, @@ -228,7 +228,6 @@ export function resetPlaybackScope($scope: GlobalScope): void { $scope.playMethod = ''; $scope.canSeek = false; - $scope.canClientSeek = false; $scope.isChangingStream = false; $scope.playNextItem = true; @@ -811,23 +810,6 @@ export async function translateRequestedItems( }; } -/** - * Take all properties of source and copy them over to target - * - * TODO can we remove this crap - * - * @param target - object that gets populated with entries - * @param source - object that the entries are copied from - * @returns reference to target object - */ -export function extend(target: any, source: any): any { - for (const i in source) { - target[i] = source[i]; - } - - return target; -} - /** * Parse a date.. Just a wrapper around new Date, * but could be useful to deal with weird date strings @@ -859,13 +841,3 @@ export function broadcastToMessageBus(message: BusMessage): void { export function broadcastConnectionErrorMessage(): void { broadcastToMessageBus({ message: '', type: 'connectionerror' }); } - -/** - * Remove all special characters from a string - * - * @param name - input string - * @returns string with non-whitespace non-word characters removed - */ -export function cleanName(name: string): string { - return name.replace(/[^\w\s]/gi, ''); -} diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 2018cfb8..f89689e5 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -6,12 +6,6 @@ import { SystemVolumeData } from 'chromecast-caf-receiver/cast.framework.system' import { RepeatMode } from '../api/generated/models/repeat-mode'; import { BaseItemDto } from '../api/generated/models/base-item-dto'; -export interface DeviceInfo { - deviceId: string | number; - deviceName: string; - versionNumber: string; -} - export interface GlobalScope { [key: string]: any; } @@ -102,9 +96,8 @@ declare global { export const RECEIVERVERSION: string; export const $scope: GlobalScope; export interface Window { - deviceInfo: DeviceInfo; mediaElement: HTMLElement | null; - mediaManager: PlayerManager; + playerManager: PlayerManager; castReceiverContext: CastReceiverContext; playlist: Array; currentPlaylistIndex: number;