diff --git a/apps/web-backend/src/app/web-backend-app.spec.ts b/apps/web-backend/src/app/web-backend-app.spec.ts index e9ab7f2a8..83f66ed29 100644 --- a/apps/web-backend/src/app/web-backend-app.spec.ts +++ b/apps/web-backend/src/app/web-backend-app.spec.ts @@ -196,6 +196,50 @@ https://stream.example/news.m3u8`); ); }); + it('extracts KODIPROP ClearKey DRM on the /parse URL-import path', async () => { + const httpClient = new StubHttpClient(); + const kid = '00112233445566778899aabbccddeeff'; + const key = 'ffeeddccbbaa99887766554433221100'; + httpClient.queueResponse(`#EXTM3U +#EXTINF:-1 tvg-id="ck" group-title="DASH",Encrypted DASH +#KODIPROP:inputstream.adaptive.license_type=clearkey +#KODIPROP:inputstream.adaptive.license_key=${kid}:${key} +https://stream.example/enc.mpd +#EXTINF:-1 tvg-id="plain",Plain Channel +https://stream.example/live.m3u8`); + + await withServer( + createWebBackendApp({ + clientOrigins: ['http://localhost:4200'], + guid: () => 'fixed-id', + httpClient, + resolveHostname: resolvePublicHost, + }), + async (baseUrl) => { + const targetId = await registerProviderTarget( + baseUrl, + 'https://provider.example/drm.m3u' + ); + const response = await fetch( + `${baseUrl}/parse?targetId=${targetId}`, + { headers: { Origin: 'http://localhost:4200' } } + ); + const body = (await response.json()) as { + playlist: { items: Array> }; + }; + + expect(response.status).toBe(200); + expect(body.playlist.items).toHaveLength(2); + expect(body.playlist.items[0]['drm']).toEqual({ + licenseType: 'clearkey', + supported: true, + clearKeys: { [kid]: key }, + }); + expect(body.playlist.items[1]['drm']).toBeUndefined(); + } + ); + }); + it('allows browser preflight checks for provider target registration', async () => { await withServer( createWebBackendApp({ diff --git a/apps/web-backend/src/app/web-backend-app.ts b/apps/web-backend/src/app/web-backend-app.ts index 32bd75abe..1ccbba9c9 100644 --- a/apps/web-backend/src/app/web-backend-app.ts +++ b/apps/web-backend/src/app/web-backend-app.ts @@ -8,6 +8,7 @@ import axios from 'axios'; import epgParser from 'epg-parser'; import parser from 'iptv-playlist-parser'; import { normalizeXtreamServerUrl } from '@iptvnator/shared/interfaces'; +import { extractDrmFromRaw } from '@iptvnator/shared/m3u-utils'; export interface WebBackendHttpGetOptions { readonly headers?: Record; @@ -514,10 +515,20 @@ function createPlaylistObject(options: { count: options.playlist.items.length, playlist: { ...options.playlist, - items: options.playlist.items.map((item) => ({ - id: options.guid(), - ...item, - })), + items: options.playlist.items.map((item) => { + // Keep this builder aligned with the shared + // createPlaylistObject() in @iptvnator/shared/m3u-utils: + // KODIPROP ClearKey DRM must survive the /parse URL-import + // path too. + const drm = extractDrmFromRaw( + typeof item['raw'] === 'string' ? item['raw'] : undefined + ); + return { + id: options.guid(), + ...item, + ...(drm ? { drm } : {}), + }; + }), }, importDate: timestamp, lastUsage: timestamp,