mirror of
https://github.com/chrisbenincasa/tunarr.git
synced 2026-04-18 09:03:35 -04:00
fix: rework native playback api types
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -83,8 +83,8 @@ describe('mapLineupItemToPlaybackItem', () => {
|
||||
|
||||
const result = mapLineupItemToPlaybackItem(item, baseUrl, channelId);
|
||||
|
||||
expect(result.kind).toBe('content');
|
||||
if (result.kind === 'content') {
|
||||
expect(result.type).toBe('content');
|
||||
if (result.type === 'content') {
|
||||
expect(result.title).toBe(programTitle);
|
||||
expect(result.episodeTitle).toBeUndefined();
|
||||
}
|
||||
@@ -101,8 +101,8 @@ describe('mapLineupItemToPlaybackItem', () => {
|
||||
|
||||
const result = mapLineupItemToPlaybackItem(item, baseUrl, channelId);
|
||||
|
||||
expect(result.kind).toBe('content');
|
||||
if (result.kind === 'content') {
|
||||
expect(result.type).toBe('content');
|
||||
if (result.type === 'content') {
|
||||
expect(result.title).toBe(showTitle);
|
||||
expect(result.episodeTitle).toBe(episodeTitle);
|
||||
}
|
||||
@@ -118,8 +118,8 @@ describe('mapLineupItemToPlaybackItem', () => {
|
||||
|
||||
const result = mapLineupItemToPlaybackItem(item, baseUrl, channelId);
|
||||
|
||||
expect(result.kind).toBe('content');
|
||||
if (result.kind === 'content') {
|
||||
expect(result.type).toBe('content');
|
||||
if (result.type === 'content') {
|
||||
expect(result.seasonNumber).toBe(2);
|
||||
expect(result.episodeNumber).toBe(3);
|
||||
}
|
||||
@@ -131,8 +131,8 @@ describe('mapLineupItemToPlaybackItem', () => {
|
||||
|
||||
const result = mapLineupItemToPlaybackItem(item, baseUrl, channelId);
|
||||
|
||||
expect(result.kind).toBe('content');
|
||||
if (result.kind === 'content') {
|
||||
expect(result.type).toBe('content');
|
||||
if (result.type === 'content') {
|
||||
expect(result.seekOffsetMs).toBe(seekMs);
|
||||
}
|
||||
});
|
||||
@@ -142,8 +142,8 @@ describe('mapLineupItemToPlaybackItem', () => {
|
||||
|
||||
const result = mapLineupItemToPlaybackItem(item, baseUrl, channelId);
|
||||
|
||||
expect(result.kind).toBe('content');
|
||||
if (result.kind === 'content') {
|
||||
expect(result.type).toBe('content');
|
||||
if (result.type === 'content') {
|
||||
expect(result.seekOffsetMs).toBe(0);
|
||||
}
|
||||
});
|
||||
@@ -154,8 +154,8 @@ describe('mapLineupItemToPlaybackItem', () => {
|
||||
|
||||
const result = mapLineupItemToPlaybackItem(item, baseUrl, channelId);
|
||||
|
||||
expect(result.kind).toBe('content');
|
||||
if (result.kind === 'content') {
|
||||
expect(result.type).toBe('content');
|
||||
if (result.type === 'content') {
|
||||
expect(result.streamUrl).toBe(
|
||||
`${baseUrl}/stream/channels/${channelId}/item-stream.ts?t=${itemStartedAtMs}`,
|
||||
);
|
||||
@@ -168,8 +168,8 @@ describe('mapLineupItemToPlaybackItem', () => {
|
||||
|
||||
const result = mapLineupItemToPlaybackItem(item, baseUrl, channelId);
|
||||
|
||||
expect(result.kind).toBe('content');
|
||||
if (result.kind === 'content') {
|
||||
expect(result.type).toBe('content');
|
||||
if (result.type === 'content') {
|
||||
expect(result.thumb).toBe(iconUrl);
|
||||
}
|
||||
});
|
||||
@@ -184,8 +184,8 @@ describe('mapLineupItemToPlaybackItem', () => {
|
||||
|
||||
const result = mapLineupItemToPlaybackItem(offline, baseUrl, channelId);
|
||||
|
||||
expect(result.kind).toBe('flex');
|
||||
if (result.kind === 'flex') {
|
||||
expect(result.type).toBe('flex');
|
||||
if (result.type === 'flex') {
|
||||
expect(result.remainingMs).toBe(offline.streamDuration);
|
||||
expect(result.itemStartedAtMs).toBe(offline.programBeginMs);
|
||||
}
|
||||
@@ -202,6 +202,6 @@ describe('mapLineupItemToPlaybackItem', () => {
|
||||
|
||||
const result = mapLineupItemToPlaybackItem(redirect, baseUrl, channelId);
|
||||
|
||||
expect(result.kind).toBe('flex');
|
||||
expect(result.type).toBe('flex');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { RouterPluginAsyncCallback } from '@/types/serverType.js';
|
||||
import { LoggerFactory } from '@/util/logging/LoggerFactory.js';
|
||||
import type { NativePlaybackItem } from '@tunarr/types';
|
||||
import { BasicIdParamSchema } from '@tunarr/types/api';
|
||||
import { NativePlaybackResponseSchema } from '@tunarr/types/schemas';
|
||||
import { isNil } from 'lodash-es';
|
||||
import z from 'zod/v4';
|
||||
import type { StreamLineupItem } from '../db/derived_types/StreamLineup.ts';
|
||||
@@ -9,50 +11,6 @@ import {
|
||||
isOfflineLineupItem,
|
||||
} from '../db/derived_types/StreamLineup.ts';
|
||||
|
||||
const NativePlaybackContentItemSchema = z.object({
|
||||
kind: z.literal('content'),
|
||||
itemStartedAtMs: z.number().int(),
|
||||
seekOffsetMs: z.number().int(),
|
||||
remainingMs: z.number().int(),
|
||||
programId: z.string().uuid(),
|
||||
title: z.string(),
|
||||
episodeTitle: z.string().optional(),
|
||||
seasonNumber: z.number().int().optional(),
|
||||
episodeNumber: z.number().int().optional(),
|
||||
summary: z.string().optional(),
|
||||
thumb: z.string().optional(),
|
||||
streamUrl: z.string(),
|
||||
});
|
||||
|
||||
const NativePlaybackFlexItemSchema = z.object({
|
||||
kind: z.literal('flex'),
|
||||
remainingMs: z.number().int(),
|
||||
itemStartedAtMs: z.number().int(),
|
||||
});
|
||||
|
||||
const NativePlaybackErrorItemSchema = z.object({
|
||||
kind: z.literal('error'),
|
||||
message: z.string(),
|
||||
retryAfterMs: z.number().int(),
|
||||
});
|
||||
|
||||
const NativePlaybackItemSchema = z.discriminatedUnion('kind', [
|
||||
NativePlaybackContentItemSchema,
|
||||
NativePlaybackFlexItemSchema,
|
||||
NativePlaybackErrorItemSchema,
|
||||
]);
|
||||
|
||||
const NativePlaybackResponseSchema = z.object({
|
||||
channelId: z.string().uuid(),
|
||||
channelNumber: z.number().int(),
|
||||
channelName: z.string(),
|
||||
serverTimeMs: z.number().int(),
|
||||
current: NativePlaybackItemSchema,
|
||||
next: NativePlaybackItemSchema.optional(),
|
||||
});
|
||||
|
||||
type NativePlaybackItem = z.infer<typeof NativePlaybackItemSchema>;
|
||||
|
||||
function buildStreamUrl(
|
||||
baseUrl: string,
|
||||
channelId: string,
|
||||
@@ -71,7 +29,7 @@ export function mapLineupItemToPlaybackItem(
|
||||
|
||||
if (isOfflineLineupItem(lineupItem)) {
|
||||
return {
|
||||
kind: 'flex',
|
||||
type: 'flex',
|
||||
remainingMs,
|
||||
itemStartedAtMs,
|
||||
};
|
||||
@@ -80,7 +38,7 @@ export function mapLineupItemToPlaybackItem(
|
||||
if (isContentBackedLineupItem(lineupItem)) {
|
||||
const program = lineupItem.program;
|
||||
return {
|
||||
kind: 'content',
|
||||
type: 'content',
|
||||
itemStartedAtMs,
|
||||
seekOffsetMs: lineupItem.startOffset ?? 0,
|
||||
remainingMs,
|
||||
@@ -99,7 +57,7 @@ export function mapLineupItemToPlaybackItem(
|
||||
|
||||
// error or redirect items fall through as flex
|
||||
return {
|
||||
kind: 'flex',
|
||||
type: 'flex',
|
||||
remainingMs: lineupItem.streamDuration,
|
||||
itemStartedAtMs: lineupItem.programBeginMs,
|
||||
};
|
||||
@@ -167,7 +125,7 @@ export const nativePlaybackApi: RouterPluginAsyncCallback = async (fastify) => {
|
||||
channelName: channel.name,
|
||||
serverTimeMs: now,
|
||||
current: {
|
||||
kind: 'error',
|
||||
type: 'error',
|
||||
message:
|
||||
currentResult.error.message ??
|
||||
'Unable to determine current program',
|
||||
|
||||
22
types/src/NativePlayback.ts
Normal file
22
types/src/NativePlayback.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type z from 'zod/v4';
|
||||
import type {
|
||||
NativePlaybackContentItemSchema,
|
||||
NativePlaybackErrorItemSchema,
|
||||
NativePlaybackFlexItemSchema,
|
||||
NativePlaybackItemSchema,
|
||||
NativePlaybackResponseSchema,
|
||||
} from './schemas/nativePlaybackSchemas.js';
|
||||
|
||||
export type NativePlaybackContentItem = z.infer<
|
||||
typeof NativePlaybackContentItemSchema
|
||||
>;
|
||||
export type NativePlaybackFlexItem = z.infer<
|
||||
typeof NativePlaybackFlexItemSchema
|
||||
>;
|
||||
export type NativePlaybackErrorItem = z.infer<
|
||||
typeof NativePlaybackErrorItemSchema
|
||||
>;
|
||||
export type NativePlaybackItem = z.infer<typeof NativePlaybackItemSchema>;
|
||||
export type NativePlaybackResponse = z.infer<
|
||||
typeof NativePlaybackResponseSchema
|
||||
>;
|
||||
@@ -10,6 +10,7 @@ export * from './HdhrSettings.js';
|
||||
export * from './LanguagePreferences.js';
|
||||
export * from './MediaSourceSettings.js';
|
||||
export * from './misc.js';
|
||||
export * from './NativePlayback.js';
|
||||
export * from './Program.js';
|
||||
export * from './Subtitles.js';
|
||||
export * from './SystemSettings.js';
|
||||
|
||||
@@ -17,3 +17,4 @@ export * from './transcodeConfigSchemas.js';
|
||||
export * from './customShowsSchema.js';
|
||||
export * from './fillerSchema.js';
|
||||
export * from './guideApiSchemas.js';
|
||||
export * from './nativePlaybackSchemas.js';
|
||||
|
||||
45
types/src/schemas/nativePlaybackSchemas.ts
Normal file
45
types/src/schemas/nativePlaybackSchemas.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import z from 'zod/v4';
|
||||
|
||||
const NativePlaybackTimingSchema = z.object({
|
||||
itemStartedAtMs: z.number().int(),
|
||||
remainingMs: z.number().int(),
|
||||
});
|
||||
|
||||
export const NativePlaybackContentItemSchema =
|
||||
NativePlaybackTimingSchema.extend({
|
||||
type: z.literal('content'),
|
||||
seekOffsetMs: z.number().int(),
|
||||
programId: z.string().uuid(),
|
||||
title: z.string(),
|
||||
episodeTitle: z.string().optional(),
|
||||
seasonNumber: z.number().int().optional(),
|
||||
episodeNumber: z.number().int().optional(),
|
||||
summary: z.string().optional(),
|
||||
thumb: z.string().optional(),
|
||||
streamUrl: z.string(),
|
||||
});
|
||||
|
||||
export const NativePlaybackFlexItemSchema = NativePlaybackTimingSchema.extend({
|
||||
type: z.literal('flex'),
|
||||
});
|
||||
|
||||
export const NativePlaybackErrorItemSchema = z.object({
|
||||
type: z.literal('error'),
|
||||
message: z.string(),
|
||||
retryAfterMs: z.number().int(),
|
||||
});
|
||||
|
||||
export const NativePlaybackItemSchema = z.discriminatedUnion('type', [
|
||||
NativePlaybackContentItemSchema,
|
||||
NativePlaybackFlexItemSchema,
|
||||
NativePlaybackErrorItemSchema,
|
||||
]);
|
||||
|
||||
export const NativePlaybackResponseSchema = z.object({
|
||||
channelId: z.string().uuid(),
|
||||
channelNumber: z.number().int(),
|
||||
channelName: z.string(),
|
||||
serverTimeMs: z.number().int(),
|
||||
current: NativePlaybackItemSchema,
|
||||
next: NativePlaybackItemSchema.optional(),
|
||||
});
|
||||
@@ -5943,10 +5943,10 @@ export type GetApiChannelsByIdNativePlaybackResponses = {
|
||||
channelName: string;
|
||||
serverTimeMs: number;
|
||||
current: {
|
||||
kind: 'content';
|
||||
itemStartedAtMs: number;
|
||||
seekOffsetMs: number;
|
||||
remainingMs: number;
|
||||
type: 'content';
|
||||
seekOffsetMs: number;
|
||||
programId: string;
|
||||
title: string;
|
||||
episodeTitle?: string;
|
||||
@@ -5956,19 +5956,19 @@ export type GetApiChannelsByIdNativePlaybackResponses = {
|
||||
thumb?: string;
|
||||
streamUrl: string;
|
||||
} | {
|
||||
kind: 'flex';
|
||||
remainingMs: number;
|
||||
itemStartedAtMs: number;
|
||||
remainingMs: number;
|
||||
type: 'flex';
|
||||
} | {
|
||||
kind: 'error';
|
||||
type: 'error';
|
||||
message: string;
|
||||
retryAfterMs: number;
|
||||
};
|
||||
next?: {
|
||||
kind: 'content';
|
||||
itemStartedAtMs: number;
|
||||
seekOffsetMs: number;
|
||||
remainingMs: number;
|
||||
type: 'content';
|
||||
seekOffsetMs: number;
|
||||
programId: string;
|
||||
title: string;
|
||||
episodeTitle?: string;
|
||||
@@ -5978,11 +5978,11 @@ export type GetApiChannelsByIdNativePlaybackResponses = {
|
||||
thumb?: string;
|
||||
streamUrl: string;
|
||||
} | {
|
||||
kind: 'flex';
|
||||
remainingMs: number;
|
||||
itemStartedAtMs: number;
|
||||
remainingMs: number;
|
||||
type: 'flex';
|
||||
} | {
|
||||
kind: 'error';
|
||||
type: 'error';
|
||||
message: string;
|
||||
retryAfterMs: number;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user