Experimental video player

This commit is contained in:
Aleksi Lassila
2023-06-19 20:44:36 +03:00
parent 3429d2c5ee
commit b39bf97b8a
21 changed files with 21264 additions and 146 deletions

20607
src/lib/jellyfin/jellyfin-types.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,375 @@
import createClient from 'openapi-fetch';
import type { paths } from '$lib/jellyfin/jellyfin-types';
import { PUBLIC_JELLYFIN_API_KEY } from '$env/static/public';
import { request } from '$lib/utils';
export const JELLYFIN_DEVICE_ID = 'Reiverr Client';
export const JELLYFIN_BASE_URL = 'http://jellyfin.home';
export const JELLYFIN_USER_ID = '75dcb061c9404115a7acdc893ea6bbbc';
export const JellyfinApi = createClient<paths>({
baseUrl: JELLYFIN_BASE_URL,
headers: {
Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${PUBLIC_JELLYFIN_API_KEY}"`
}
});
export const getJellyfinContinueWatching = () =>
request(() =>
JellyfinApi.get('/Users/{userId}/Items/Resume', {
params: {
path: {
userId: JELLYFIN_USER_ID
},
query: {
limit: 8,
mediaTypes: ['Video']
}
}
}).then((r) => r.data?.Items)
);
export const getJellyfinItemByTmdbId = () =>
request((tmdbId: string) =>
JellyfinApi.get('/Users/{userId}/Items', {
params: {
path: {
userId: JELLYFIN_USER_ID
},
query: {
hasTmdbId: true,
recursive: true,
isMovie: true,
fields: ['ProviderIds']
}
}
}).then((r) => r.data?.Items?.find((i) => i.ProviderIds?.Tmdb == tmdbId))
);
export const getJellyfinPlaybackInfo = () =>
request((id: string) =>
JellyfinApi.post('/Items/{itemId}/PlaybackInfo', {
params: {
path: {
itemId: id
},
query: {
userId: JELLYFIN_USER_ID,
startTimeTicks: 0,
autoOpenLiveStream: true,
maxStreamingBitrate: 140000000
}
},
body: {
DeviceProfile: {
CodecProfiles: [
{
Codec: 'aac',
Conditions: [
{
Condition: 'Equals',
IsRequired: false,
Property: 'IsSecondaryAudio',
Value: 'false'
}
],
Type: 'VideoAudio'
},
{
Conditions: [
{
Condition: 'Equals',
IsRequired: false,
Property: 'IsSecondaryAudio',
Value: 'false'
}
],
Type: 'VideoAudio'
},
{
Codec: 'h264',
Conditions: [
{
Condition: 'NotEquals',
IsRequired: false,
Property: 'IsAnamorphic',
Value: 'true'
},
{
Condition: 'EqualsAny',
IsRequired: false,
Property: 'VideoProfile',
Value: 'high|main|baseline|constrained baseline'
},
{
Condition: 'EqualsAny',
IsRequired: false,
Property: 'VideoRangeType',
Value: 'SDR'
},
{
Condition: 'LessThanEqual',
IsRequired: false,
Property: 'VideoLevel',
Value: '52'
},
{
Condition: 'NotEquals',
IsRequired: false,
Property: 'IsInterlaced',
Value: 'true'
}
],
Type: 'Video'
},
{
Codec: 'hevc',
Conditions: [
{
Condition: 'NotEquals',
IsRequired: false,
Property: 'IsAnamorphic',
Value: 'true'
},
{
Condition: 'EqualsAny',
IsRequired: false,
Property: 'VideoProfile',
Value: 'main'
},
{
Condition: 'EqualsAny',
IsRequired: false,
Property: 'VideoRangeType',
Value: 'SDR'
},
{
Condition: 'LessThanEqual',
IsRequired: false,
Property: 'VideoLevel',
Value: '120'
},
{
Condition: 'NotEquals',
IsRequired: false,
Property: 'IsInterlaced',
Value: 'true'
}
],
Type: 'Video'
},
{
Codec: 'vp9',
Conditions: [
{
Condition: 'EqualsAny',
IsRequired: false,
Property: 'VideoRangeType',
Value: 'SDR|HDR10|HLG'
}
],
Type: 'Video'
},
{
Codec: 'av1',
Conditions: [
{
Condition: 'EqualsAny',
IsRequired: false,
Property: 'VideoRangeType',
Value: 'SDR|HDR10|HLG'
}
],
Type: 'Video'
}
],
ContainerProfiles: [],
DirectPlayProfiles: [
{
AudioCodec: 'vorbis,opus',
Container: 'webm',
Type: 'Video',
VideoCodec: 'vp8,vp9,av1'
},
{
AudioCodec: 'aac,mp3,opus,flac,alac,vorbis',
Container: 'mp4,m4v',
Type: 'Video',
VideoCodec: 'h264,vp9,av1'
},
{
Container: 'opus',
Type: 'Audio'
},
{
AudioCodec: 'opus',
Container: 'webm',
Type: 'Audio'
},
{
Container: 'mp3',
Type: 'Audio'
},
{
Container: 'aac',
Type: 'Audio'
},
{
AudioCodec: 'aac',
Container: 'm4a',
Type: 'Audio'
},
{
AudioCodec: 'aac',
Container: 'm4b',
Type: 'Audio'
},
{
Container: 'flac',
Type: 'Audio'
},
{
Container: 'alac',
Type: 'Audio'
},
{
AudioCodec: 'alac',
Container: 'm4a',
Type: 'Audio'
},
{
AudioCodec: 'alac',
Container: 'm4b',
Type: 'Audio'
},
{
Container: 'webma',
Type: 'Audio'
},
{
AudioCodec: 'webma',
Container: 'webm',
Type: 'Audio'
},
{
Container: 'wav',
Type: 'Audio'
},
{
Container: 'ogg',
Type: 'Audio'
}
],
MaxStaticBitrate: 100000000,
MaxStreamingBitrate: 120000000,
MusicStreamingTranscodingBitrate: 384000,
ResponseProfiles: [
{
Container: 'm4v',
MimeType: 'video/mp4',
Type: 'Video'
}
],
SubtitleProfiles: [
{
Format: 'vtt',
Method: 'External'
},
{
Format: 'ass',
Method: 'External'
},
{
Format: 'ssa',
Method: 'External'
}
],
TranscodingProfiles: [
{
AudioCodec: 'aac',
BreakOnNonKeyFrames: true,
Container: 'ts',
Context: 'Streaming',
MaxAudioChannels: '2',
Protocol: 'hls',
Type: 'Audio'
},
{
AudioCodec: 'aac',
Container: 'aac',
Context: 'Streaming',
MaxAudioChannels: '2',
Protocol: 'http',
Type: 'Audio'
},
{
AudioCodec: 'mp3',
Container: 'mp3',
Context: 'Streaming',
MaxAudioChannels: '2',
Protocol: 'http',
Type: 'Audio'
},
{
AudioCodec: 'opus',
Container: 'opus',
Context: 'Streaming',
MaxAudioChannels: '2',
Protocol: 'http',
Type: 'Audio'
},
{
AudioCodec: 'wav',
Container: 'wav',
Context: 'Streaming',
MaxAudioChannels: '2',
Protocol: 'http',
Type: 'Audio'
},
{
AudioCodec: 'opus',
Container: 'opus',
Context: 'Static',
MaxAudioChannels: '2',
Protocol: 'http',
Type: 'Audio'
},
{
AudioCodec: 'mp3',
Container: 'mp3',
Context: 'Static',
MaxAudioChannels: '2',
Protocol: 'http',
Type: 'Audio'
},
{
AudioCodec: 'aac',
Container: 'aac',
Context: 'Static',
MaxAudioChannels: '2',
Protocol: 'http',
Type: 'Audio'
},
{
AudioCodec: 'wav',
Container: 'wav',
Context: 'Static',
MaxAudioChannels: '2',
Protocol: 'http',
Type: 'Audio'
},
{
AudioCodec: 'aac,mp3',
BreakOnNonKeyFrames: true,
Container: 'ts',
Context: 'Streaming',
MaxAudioChannels: '2',
Protocol: 'hls',
Type: 'Video',
VideoCodec: 'h264'
}
]
}
}
}).then((r) => r.data?.MediaSources?.[0]?.TranscodingUrl)
);

View File

@@ -1,8 +1,8 @@
import createClient from 'openapi-fetch';
import { PUBLIC_RADARR_API_KEY } from '$env/static/public';
import { request } from '$lib/utils';
import type { paths } from '$lib/radarr/radarr-api';
import type { components } from '$lib/radarr/radarr-api';
import type { paths } from '$lib/radarr/radarr-types';
import type { components } from '$lib/radarr/radarr-types';
import type { TmdbMovie, TmdbMovieFull } from '$lib/tmdb-api';
import { fetchTmdbMovie, TmdbApi } from '$lib/tmdb-api';

View File

@@ -19,13 +19,13 @@ export async function fetchFullMovieDetails(tmdbId: string): Promise<TmdbMovieFu
};
}
export const fetchTmdbMovie = async (tmdbId: string) =>
export const fetchTmdbMovie = async (tmdbId: string): Promise<TmdbMovie> =>
await TmdbApi.get<TmdbMovie>('/movie/' + tmdbId).then((r) => r.data);
export const fetchTmdbMovieVideos = async (tmdbId: string) =>
export const fetchTmdbMovieVideos = async (tmdbId: string): Promise<Video[]> =>
await TmdbApi.get<VideosResponse>('/movie/' + tmdbId + '/videos').then((res) => res.data.results);
export const fetchTmdbMovieImages = async (tmdbId: string) =>
export const fetchTmdbMovieImages = async (tmdbId: string): Promise<ImagesResponse> =>
await TmdbApi.get<ImagesResponse>('/movie/' + tmdbId + '/images').then((res) => res.data);
export interface TmdbMovieFull extends TmdbMovie {

View File

@@ -1,3 +1,3 @@
import type { components as sonarrComponents } from '$lib/sonarr/sonarr-api';
import type { components as sonarrComponents } from '$lib/sonarr/sonarr-types';
export type SeriesResource = sonarrComponents['schemas']['SeriesResource'];

View File

@@ -35,7 +35,7 @@ export function request<T, A>(fetcher: (arg: A) => Promise<T>, args: A | undefin
fetcher(arg)
.then((d) => {
console.log('got data', d);
console.log('request data', d);
data.set(d);
})
.catch((e) => error.set(e))