-
-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Misc fixes #107
Misc fixes #107
Changes from all commits
353fd6e
0653924
20bf120
71cac20
5f96fe0
114aab7
18b3db2
a76e8b8
94aba9c
921467a
482cf58
4f17065
c8b1b15
f613aee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,13 +19,12 @@ import { | |
|
||
import { reportPlaybackProgress } from './jellyfinActions'; | ||
|
||
import { playbackManager } from './playbackManager'; | ||
import { PlaybackManager } from './playbackManager'; | ||
|
||
import { DocumentManager } from './documentManager'; | ||
|
||
export abstract class CommandHandler { | ||
private static playerManager: framework.PlayerManager; | ||
private static playbackManager: playbackManager; | ||
private static supportedCommands: SupportedCommands = { | ||
DisplayContent: CommandHandler.displayContentHandler, | ||
Identify: CommandHandler.IdentifyHandler, | ||
|
@@ -52,12 +51,8 @@ export abstract class CommandHandler { | |
VolumeUp: CommandHandler.VolumeUpHandler | ||
}; | ||
|
||
static configure( | ||
playerManager: framework.PlayerManager, | ||
playbackManager: playbackManager | ||
): void { | ||
static configure(playerManager: framework.PlayerManager): void { | ||
this.playerManager = playerManager; | ||
this.playbackManager = playbackManager; | ||
} | ||
|
||
static playNextHandler(data: DataMessage): void { | ||
|
@@ -89,23 +84,20 @@ export abstract class CommandHandler { | |
} | ||
|
||
static displayContentHandler(data: DataMessage): void { | ||
if (!this.playbackManager.isPlaying()) { | ||
if (!PlaybackManager.isPlaying()) { | ||
DocumentManager.showItemId((<DisplayRequest>data.options).ItemId); | ||
} | ||
} | ||
|
||
static nextTrackHandler(): void { | ||
if ( | ||
window.playlist && | ||
window.currentPlaylistIndex < window.playlist.length - 1 | ||
) { | ||
this.playbackManager.playNextItem({}, true); | ||
if (PlaybackManager.hasNextItem()) { | ||
PlaybackManager.playNextItem({}, true); | ||
} | ||
} | ||
|
||
static previousTrackHandler(): void { | ||
if (window.playlist && window.currentPlaylistIndex > 0) { | ||
this.playbackManager.playPreviousItem({}); | ||
if (PlaybackManager.hasPrevItem()) { | ||
PlaybackManager.playPreviousItem({}); | ||
} | ||
} | ||
|
||
|
@@ -138,7 +130,8 @@ export abstract class CommandHandler { | |
} | ||
|
||
static IdentifyHandler(): void { | ||
if (!this.playbackManager.isPlaying()) { | ||
if (!PlaybackManager.isPlaying()) { | ||
DocumentManager.setAppStatus('waiting'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we make these app status an enum? DocumentManager seems a good place to declare it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in hawken93#228 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I tried latest master on jellyfin/jellyfin and jellyfin/jellyfin-web, The identify command was sent from the client while buffering (after load). isPlaying() returned false here and the receiver went back to "waiting" state (i.e. "welcome screen") while the video played behind it. Got the same behavior every time I tried. When I removed these actions, it works as expected. Should this logic really be in the identify command handler? For me, this is looks like side effect. I think more robust guards than isPlaying check must be in place if this is kept. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think of this solution? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks promising and I am happy with it if it works. |
||
DocumentManager.startBackdropInterval(); | ||
} else { | ||
// When a client connects send back the initial device state (volume etc) via a playbackstop message | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this type was never actually PlaybackProgressInfo. The scripting throws other properties at it as well. That has to be resolved somehow first, but this looks to be a major refactor on its own There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I remember. This had to be done because I removed the silly extend() function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So is this staying as-is? |
||
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'); | ||
|
@@ -241,15 +250,6 @@ export function play($scope: GlobalScope): void { | |
} | ||
} | ||
|
||
/** | ||
* Don't actually stop, just show the idle view after 20ms | ||
*/ | ||
export function stop(): void { | ||
setTimeout(() => { | ||
DocumentManager.setAppStatus('waiting'); | ||
}, 20); | ||
} | ||
|
||
/** | ||
* @param item | ||
* @param maxBitrate | ||
|
@@ -369,11 +369,14 @@ export async function getDownloadSpeed(byteSize: number): Promise<number> { | |
|
||
const now = new Date().getTime(); | ||
|
||
await JellyfinApi.authAjax(path, { | ||
const response = await JellyfinApi.authAjax(path, { | ||
timeout: 5000, | ||
type: 'GET' | ||
}); | ||
|
||
// Force javascript to download the whole response before calculating bitrate | ||
await response.blob(); | ||
|
||
const responseTimeSeconds = (new Date().getTime() - now) / 1000; | ||
const bytesPerSecond = byteSize / responseTimeSeconds; | ||
const bitrate = Math.round(bytesPerSecond * 8); | ||
|
@@ -383,22 +386,29 @@ export async function getDownloadSpeed(byteSize: number): Promise<number> { | |
|
||
/** | ||
* Function to detect the bitrate. | ||
* It first tries 1MB and if bitrate is above 1Mbit/s it tries again with 2.4MB. | ||
* It starts at 500kB and doubles it every time it takes under 2s, for max 10MB. | ||
* This should get an accurate bitrate relatively fast on any connection | ||
* | ||
* @param numBytes - Number of bytes to start with, default 500k | ||
* @returns bitrate in bits/s | ||
*/ | ||
export async function detectBitrate(): Promise<number> { | ||
// First try a small amount so that we don't hang up their mobile connection | ||
export async function detectBitrate(numBytes = 500000): Promise<number> { | ||
// Jellyfin has a 10MB limit on the test size | ||
const byteLimit = 10000000; | ||
|
||
let bitrate = await getDownloadSpeed(1000000); | ||
|
||
if (bitrate < 1000000) { | ||
return Math.round(bitrate * 0.8); | ||
if (numBytes > byteLimit) { | ||
numBytes = byteLimit; | ||
} | ||
|
||
bitrate = await getDownloadSpeed(2400000); | ||
const bitrate = await getDownloadSpeed(numBytes); | ||
|
||
return Math.round(bitrate * 0.8); | ||
if (bitrate * (2 / 8.0) < numBytes || numBytes >= byteLimit) { | ||
// took > 2s, or numBytes hit the limit | ||
return Math.round(bitrate * 0.8); | ||
} else { | ||
// If that produced a fairly high speed, try again with a larger size to get a more accurate result | ||
return await detectBitrate(numBytes * 2); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -409,7 +419,7 @@ export async function detectBitrate(): Promise<number> { | |
*/ | ||
export function stopActiveEncodings($scope: GlobalScope): Promise<void> { | ||
const options = { | ||
deviceId: window.deviceInfo.deviceId, | ||
deviceId: JellyfinApi.deviceId, | ||
PlaySessionId: undefined | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,28 +11,55 @@ 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: string | ||
): 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, ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move inside null-check below, I got null-exception here when I tried it. Edit: Android app seem to send null receiverName while Chrome browser sets this to non-null. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
if (receiverName) { | ||
this.deviceName = receiverName; | ||
// deviceId just needs to be unique-ish | ||
this.deviceId = btoa(receiverName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is nitpicky but why not generate an UUID or something else? a base64 representation of the device's name is not exactly an id. We can also use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're probably right. This code has been refactored from maincontroller or stable, don't remember which exactly. It fixes an error in execution order where nightly will register with the server before the device identity has been set, so after these properties are set it would identify itself differently and hence leave ghost entries in the server device list. This is the part that I fixed, so my idea here is to just keep it as-is for now, and then we can try to change it and assert that it did not break later on. |
||
} 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 | ||
private static getSecurityHeaders(): Dictionary<string> { | ||
// TODO throw error if this fails | ||
|
||
let auth = | ||
`Emby Client="Chromecast", ` + | ||
`Device="${window.deviceInfo.deviceName}", ` + | ||
`DeviceId="${window.deviceInfo.deviceId}", ` + | ||
`Version="${window.deviceInfo.versionNumber}"`; | ||
`Emby Client="Chromecast", Device="${this.deviceName}", ` + | ||
`DeviceId="${this.deviceId}", Version="${this.versionNumber}"`; | ||
|
||
if (this.userId) { | ||
auth += `, UserId="${this.userId}"`; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This causes videos to be transcodeded even though they can be direct played (setups: CCUltra/4K-TV and CCGen3/FHD-TV). window.innerWidth was 1280 when I tried with gen 2/3 chromecast and a FHD TV. 1920 would be the preferred MaxWidth in that case.
Maybe only use window.innerWidth when HLS stream?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ownbee what should the value be if it's not an HLS stream (instead of
window.innerWidth
) ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this look?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tnyeanderson I think they should be clamped depending on CC-type as it was before. For direct play I think the CC/TV is able to automatically scale if the resolution is too big for the screen (it has worked before). For HLS I have no clue how to solve it.
I fail to see how
h264
coding has anything to do with MaxWidthSupport. If I play a 1920x1080 video withh264
coding on my FHD TV (1920x1080) do I get thewindow.innerWidth
constraint? If so, I would say this is not that great since 95% of my movies are in that format and would be transcoded to lower resolution and put a lot of load on my server.Problem is, I think, is that the jellyfin server is the one that decides if transcoding is needed and how to stream depending on the constraints chromecast-app reports. Which means this logic suits best to be in the jellyfin server or if we could find something better than
window.innerWidth
or if we could tell it "1920x1080 is okay but if you are going to transcode for other reasons, usewindow.innerWidth
".I'll try find time to test if this works and how
window.innerWidth
behaves this weekend but I can't make a promise.Thanks for your effort trying to get this in! Really appreciated!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found some relevant info regarding this: https://stackoverflow.com/a/47851190
If we cannot find a better alternative to
window.innerWidth
that does not involve css, I think allwindow.innerWidth
stuff should be dropped completely and maybe added in a separate PR that focus on HLS.