Play Viaplay catch-up from the KAP window start, not the live edge.
This commit is contained in:
parent
721a2d1f2a
commit
f35200e0c3
7 changed files with 125 additions and 28 deletions
|
|
@ -345,7 +345,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 71;
|
CURRENT_PROJECT_VERSION = 72;
|
||||||
DEVELOPMENT_TEAM = X25NYX3K49;
|
DEVELOPMENT_TEAM = X25NYX3K49;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|
@ -360,7 +360,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks"
|
"@executable_path/Frameworks"
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.5.58;
|
MARKETING_VERSION = 0.5.59;
|
||||||
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
@ -378,7 +378,7 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 71;
|
CURRENT_PROJECT_VERSION = 72;
|
||||||
DEVELOPMENT_TEAM = X25NYX3K49;
|
DEVELOPMENT_TEAM = X25NYX3K49;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
|
|
@ -393,7 +393,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks"
|
"@executable_path/Frameworks"
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.5.58;
|
MARKETING_VERSION = 0.5.59;
|
||||||
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
OTHER_LDFLAGS = ("$(inherited)", "-ObjC");
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
PRODUCT_BUNDLE_IDENTIFIER = nl.vonas.mediacluster.ios;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,8 @@ final class AVLivePlayerController: NSObject, ObservableObject {
|
||||||
fallback: URL? = nil,
|
fallback: URL? = nil,
|
||||||
format: String? = nil,
|
format: String? = nil,
|
||||||
clearKeys: [ClearKeyEntry] = [],
|
clearKeys: [ClearKeyEntry] = [],
|
||||||
browserCdnHeaders: Bool = false
|
browserCdnHeaders: Bool = false,
|
||||||
|
forceVod: Bool = false
|
||||||
) async {
|
) async {
|
||||||
generation &+= 1
|
generation &+= 1
|
||||||
let gen = generation
|
let gen = generation
|
||||||
|
|
@ -80,7 +81,8 @@ final class AVLivePlayerController: NSObject, ObservableObject {
|
||||||
manifestURL: url,
|
manifestURL: url,
|
||||||
keys: clearKeys,
|
keys: clearKeys,
|
||||||
preferHlsForAVPlayer: true,
|
preferHlsForAVPlayer: true,
|
||||||
browserCdnHeaders: browserCdnHeaders
|
browserCdnHeaders: browserCdnHeaders,
|
||||||
|
forceVod: forceVod
|
||||||
)
|
)
|
||||||
guard gen == generation, !stopping else { return }
|
guard gen == generation, !stopping else { return }
|
||||||
playURL = started.url
|
playURL = started.url
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@ enum ViaplayPlaybackHeaders {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func isKapWindowURL(_ url: URL) -> Bool {
|
||||||
|
url.path.range(of: #"/start/\d+/stop/\d+"#, options: .regularExpression) != nil
|
||||||
|
}
|
||||||
|
|
||||||
static func apply(to req: inout URLRequest) {
|
static func apply(to req: inout URLRequest) {
|
||||||
// User-Agent NIET op URLRequest zetten: CFNetwork vervangt die dan door VMC/… CFNetwork/…
|
// User-Agent NIET op URLRequest zetten: CFNetwork vervangt die dan door VMC/… CFNetwork/…
|
||||||
req.setValue(referer, forHTTPHeaderField: "Referer")
|
req.setValue(referer, forHTTPHeaderField: "Referer")
|
||||||
|
|
@ -144,6 +148,8 @@ final class ClearKeyDashProxy {
|
||||||
private var isHlsMode = false
|
private var isHlsMode = false
|
||||||
/// AVPlayer kan geen DASH: serve clear HLS (native of vanuit MPD gegenereerd).
|
/// AVPlayer kan geen DASH: serve clear HLS (native of vanuit MPD gegenereerd).
|
||||||
private var preferHlsOutput = false
|
private var preferHlsOutput = false
|
||||||
|
/// Catch-up/KAP: HLS vanaf het begin van het venster, niet live-edge.
|
||||||
|
private var forceVodPlaylist = false
|
||||||
/// Segment-CDN's zonder "viaplay" in de host krijgen dezelfde browser-UA als het manifest.
|
/// Segment-CDN's zonder "viaplay" in de host krijgen dezelfde browser-UA als het manifest.
|
||||||
private var useViaplayBrowserHeaders = false
|
private var useViaplayBrowserHeaders = false
|
||||||
private var dashVideoRep: DashHlsRep?
|
private var dashVideoRep: DashHlsRep?
|
||||||
|
|
@ -274,7 +280,8 @@ final class ClearKeyDashProxy {
|
||||||
manifestURL: URL,
|
manifestURL: URL,
|
||||||
keys: [ClearKeyEntry],
|
keys: [ClearKeyEntry],
|
||||||
preferHlsForAVPlayer: Bool = false,
|
preferHlsForAVPlayer: Bool = false,
|
||||||
browserCdnHeaders: Bool = false
|
browserCdnHeaders: Bool = false,
|
||||||
|
forceVod: Bool = false
|
||||||
) async throws -> StartResult {
|
) async throws -> StartResult {
|
||||||
let map = ClearKeyKeyMap(entries: keys)
|
let map = ClearKeyKeyMap(entries: keys)
|
||||||
guard !map.isEmpty else {
|
guard !map.isEmpty else {
|
||||||
|
|
@ -293,11 +300,15 @@ final class ClearKeyDashProxy {
|
||||||
self.keyMap = map
|
self.keyMap = map
|
||||||
self.isHlsMode = hls
|
self.isHlsMode = hls
|
||||||
self.preferHlsOutput = preferHlsForAVPlayer
|
self.preferHlsOutput = preferHlsForAVPlayer
|
||||||
|
self.forceVodPlaylist = forceVod
|
||||||
self.useViaplayBrowserHeaders =
|
self.useViaplayBrowserHeaders =
|
||||||
browserCdnHeaders || ViaplayPlaybackHeaders.matches(manifestURL)
|
browserCdnHeaders || ViaplayPlaybackHeaders.matches(manifestURL)
|
||||||
if self.useViaplayBrowserHeaders {
|
if self.useViaplayBrowserHeaders {
|
||||||
proxyLog.info("CDN headers=Chrome/122 for whole session")
|
proxyLog.info("CDN headers=Chrome/122 for whole session")
|
||||||
}
|
}
|
||||||
|
if forceVod {
|
||||||
|
proxyLog.info("catch-up/KAP: VOD HLS from window start (not live-edge)")
|
||||||
|
}
|
||||||
self.dashVideoRep = nil
|
self.dashVideoRep = nil
|
||||||
self.dashAudioRep = nil
|
self.dashAudioRep = nil
|
||||||
self.dashAudioReps = []
|
self.dashAudioReps = []
|
||||||
|
|
@ -518,6 +529,7 @@ final class ClearKeyDashProxy {
|
||||||
keyMap = nil
|
keyMap = nil
|
||||||
isHlsMode = false
|
isHlsMode = false
|
||||||
preferHlsOutput = false
|
preferHlsOutput = false
|
||||||
|
forceVodPlaylist = false
|
||||||
useViaplayBrowserHeaders = false
|
useViaplayBrowserHeaders = false
|
||||||
dashVideoRep = nil
|
dashVideoRep = nil
|
||||||
dashAudioRep = nil
|
dashAudioRep = nil
|
||||||
|
|
@ -1564,8 +1576,11 @@ final class ClearKeyDashProxy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func parseDashReps(_ xml: String, mpdURL: URL) -> [ParsedRep] {
|
private func parseDashReps(_ xml: String, mpdURL: URL) -> [ParsedRep] {
|
||||||
let isLive = xml.range(of: #"type\s*=\s*"dynamic""#, options: [.regularExpression, .caseInsensitive]) != nil
|
let vod = withState { self.forceVodPlaylist } || ViaplayPlaybackHeaders.isKapWindowURL(mpdURL)
|
||||||
|
let isLive = !vod && (
|
||||||
|
xml.range(of: #"type\s*=\s*"dynamic""#, options: [.regularExpression, .caseInsensitive]) != nil
|
||||||
|| xml.range(of: #"type\s*=\s*'dynamic'"#, options: [.regularExpression, .caseInsensitive]) != nil
|
|| xml.range(of: #"type\s*=\s*'dynamic'"#, options: [.regularExpression, .caseInsensitive]) != nil
|
||||||
|
)
|
||||||
var availabilityStart: Date?
|
var availabilityStart: Date?
|
||||||
if let m = xml.range(of: #"availabilityStartTime\s*=\s*"([^"]+)""#, options: [.regularExpression, .caseInsensitive]) {
|
if let m = xml.range(of: #"availabilityStartTime\s*=\s*"([^"]+)""#, options: [.regularExpression, .caseInsensitive]) {
|
||||||
let attr = String(xml[m])
|
let attr = String(xml[m])
|
||||||
|
|
@ -1575,9 +1590,9 @@ final class ClearKeyDashProxy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Live multi-period: pak laatste Period (actuele edge).
|
// Live multi-period: pak laatste Period (actuele edge). Catch-up/KAP: hele venster.
|
||||||
var scopeXML = xml
|
var scopeXML = xml
|
||||||
if let periodRegex = try? NSRegularExpression(
|
if !vod, let periodRegex = try? NSRegularExpression(
|
||||||
pattern: #"<Period\b([^>]*)>([\s\S]*?)</Period>"#,
|
pattern: #"<Period\b([^>]*)>([\s\S]*?)</Period>"#,
|
||||||
options: [.caseInsensitive]
|
options: [.caseInsensitive]
|
||||||
) {
|
) {
|
||||||
|
|
@ -1849,8 +1864,6 @@ final class ClearKeyDashProxy {
|
||||||
var urls = rep.segmentURLs
|
var urls = rep.segmentURLs
|
||||||
if rep.isLive, urls.count > 12 {
|
if rep.isLive, urls.count > 12 {
|
||||||
urls = Array(urls.suffix(12))
|
urls = Array(urls.suffix(12))
|
||||||
} else if urls.count > 20 {
|
|
||||||
urls = Array(urls.suffix(20))
|
|
||||||
}
|
}
|
||||||
windowStartIndex = max(0, rep.segmentURLs.count - urls.count)
|
windowStartIndex = max(0, rep.segmentURLs.count - urls.count)
|
||||||
for path in urls {
|
for path in urls {
|
||||||
|
|
@ -1894,16 +1907,17 @@ final class ClearKeyDashProxy {
|
||||||
edgePad = max(0, total - 3)
|
edgePad = max(0, total - 3)
|
||||||
}
|
}
|
||||||
let end = max(0, total - edgePad)
|
let end = max(0, total - edgePad)
|
||||||
// $Time$ (Viaplay/Ziggo): 10 segs. $Number$ (Prime/HBO): 5 segs ≈10s — sneller bufferen.
|
// Catch-up/VOD: hele venster vanaf het begin. Live: kort stuk achter de edge.
|
||||||
let windowLen: Int
|
let windowLen: Int
|
||||||
if !rep.isLive {
|
if !rep.isLive {
|
||||||
windowLen = 12
|
windowLen = min(end, 5000)
|
||||||
} else if preferNumber {
|
} else if preferNumber {
|
||||||
windowLen = 5
|
windowLen = 5
|
||||||
} else {
|
} else {
|
||||||
windowLen = 10
|
windowLen = 10
|
||||||
}
|
}
|
||||||
let start = max(0, end - windowLen)
|
let start = rep.isLive ? max(0, end - windowLen) : 0
|
||||||
|
let clipEnd = min(end, start + windowLen)
|
||||||
windowStartIndex = start
|
windowStartIndex = start
|
||||||
// Alleen het venster materialiseren (Prime/HBO: r≈1800).
|
// Alleen het venster materialiseren (Prime/HBO: r≈1800).
|
||||||
var absIndex = 0
|
var absIndex = 0
|
||||||
|
|
@ -1911,7 +1925,7 @@ final class ClearKeyDashProxy {
|
||||||
let runCount = max(0, run.count)
|
let runCount = max(0, run.count)
|
||||||
let runEnd = absIndex + runCount
|
let runEnd = absIndex + runCount
|
||||||
let overlapStart = max(start, absIndex)
|
let overlapStart = max(start, absIndex)
|
||||||
let overlapEnd = min(end, runEnd)
|
let overlapEnd = min(clipEnd, runEnd)
|
||||||
if overlapStart < overlapEnd {
|
if overlapStart < overlapEnd {
|
||||||
for i in overlapStart..<overlapEnd {
|
for i in overlapStart..<overlapEnd {
|
||||||
let offsetInRun = i - absIndex
|
let offsetInRun = i - absIndex
|
||||||
|
|
@ -1934,7 +1948,7 @@ final class ClearKeyDashProxy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
absIndex = runEnd
|
absIndex = runEnd
|
||||||
if absIndex >= end { break }
|
if absIndex >= clipEnd { break }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Alleen $Number$ zonder SegmentTimeline (Prime/HBO fallback).
|
// Alleen $Number$ zonder SegmentTimeline (Prime/HBO fallback).
|
||||||
|
|
@ -1996,6 +2010,9 @@ final class ClearKeyDashProxy {
|
||||||
"#EXT-X-MEDIA-SEQUENCE:\(mediaSequence)",
|
"#EXT-X-MEDIA-SEQUENCE:\(mediaSequence)",
|
||||||
"#EXT-X-INDEPENDENT-SEGMENTS",
|
"#EXT-X-INDEPENDENT-SEGMENTS",
|
||||||
]
|
]
|
||||||
|
if !rep.isLive {
|
||||||
|
lines.append("#EXT-X-PLAYLIST-TYPE:VOD")
|
||||||
|
}
|
||||||
if !rep.initialization.isEmpty {
|
if !rep.initialization.isEmpty {
|
||||||
let initURL = resolveTemplateURL(
|
let initURL = resolveTemplateURL(
|
||||||
rep.initialization,
|
rep.initialization,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,12 @@ struct PlayRequest: Identifiable {
|
||||||
|| (watchId ?? "").localizedCaseInsensitiveContains("viaplay")
|
|| (watchId ?? "").localizedCaseInsensitiveContains("viaplay")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// KAP-terugkijken is een live-MPD met start/stop — niet naar live-edge (Don't go).
|
||||||
|
var playAsVod: Bool {
|
||||||
|
if !isLive { return true }
|
||||||
|
return url.path.range(of: #"/start/\d+/stop/\d+"#, options: .regularExpression) != nil
|
||||||
|
}
|
||||||
|
|
||||||
static func vod(
|
static func vod(
|
||||||
url: URL,
|
url: URL,
|
||||||
title: String,
|
title: String,
|
||||||
|
|
@ -586,7 +592,8 @@ struct PlayerScreen: View {
|
||||||
format: request.format,
|
format: request.format,
|
||||||
clearKeys: keys,
|
clearKeys: keys,
|
||||||
preferHls: true,
|
preferHls: true,
|
||||||
browserCdnHeaders: request.usesViaplayCdn
|
browserCdnHeaders: request.usesViaplayCdn,
|
||||||
|
forceVod: request.playAsVod
|
||||||
)
|
)
|
||||||
let maxWaitNs: UInt64 = 18_000_000_000
|
let maxWaitNs: UInt64 = 18_000_000_000
|
||||||
let stepNs: UInt64 = 200_000_000
|
let stepNs: UInt64 = 200_000_000
|
||||||
|
|
@ -605,7 +612,8 @@ struct PlayerScreen: View {
|
||||||
fallback: request.fallbackUrl,
|
fallback: request.fallbackUrl,
|
||||||
format: request.format,
|
format: request.format,
|
||||||
clearKeys: keys,
|
clearKeys: keys,
|
||||||
browserCdnHeaders: request.usesViaplayCdn
|
browserCdnHeaders: request.usesViaplayCdn,
|
||||||
|
forceVod: request.playAsVod
|
||||||
)
|
)
|
||||||
let maxWaitNs: UInt64 = 18_000_000_000
|
let maxWaitNs: UInt64 = 18_000_000_000
|
||||||
let stepNs: UInt64 = 200_000_000
|
let stepNs: UInt64 = 200_000_000
|
||||||
|
|
@ -632,7 +640,8 @@ struct PlayerScreen: View {
|
||||||
format: request.format,
|
format: request.format,
|
||||||
clearKeys: keys,
|
clearKeys: keys,
|
||||||
preferHls: true,
|
preferHls: true,
|
||||||
browserCdnHeaders: request.usesViaplayCdn
|
browserCdnHeaders: request.usesViaplayCdn,
|
||||||
|
forceVod: request.playAsVod
|
||||||
)
|
)
|
||||||
waited = 0
|
waited = 0
|
||||||
while waited < 8_000_000_000 && !vlc.isPlaying && !vlc.hasError {
|
while waited < 8_000_000_000 && !vlc.isPlaying && !vlc.hasError {
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,8 @@ final class VLCPlayerController: NSObject, ObservableObject, VLCMediaPlayerDeleg
|
||||||
format: String? = nil,
|
format: String? = nil,
|
||||||
clearKeys: [ClearKeyEntry] = [],
|
clearKeys: [ClearKeyEntry] = [],
|
||||||
preferHls: Bool = false,
|
preferHls: Bool = false,
|
||||||
browserCdnHeaders: Bool = false
|
browserCdnHeaders: Bool = false,
|
||||||
|
forceVod: Bool = false
|
||||||
) async {
|
) async {
|
||||||
// Annuleer pending finishStop / delayed proxy-stop van vorige sessie.
|
// Annuleer pending finishStop / delayed proxy-stop van vorige sessie.
|
||||||
stopGeneration &+= 1
|
stopGeneration &+= 1
|
||||||
|
|
@ -296,7 +297,7 @@ final class VLCPlayerController: NSObject, ObservableObject, VLCMediaPlayerDeleg
|
||||||
restoreAudioHint = ""
|
restoreAudioHint = ""
|
||||||
knownVodDurationMs = 0
|
knownVodDurationMs = 0
|
||||||
seekOriginMs = 0
|
seekOriginMs = 0
|
||||||
liveMode = true
|
liveMode = !forceVod
|
||||||
usePipDrawable = false
|
usePipDrawable = false
|
||||||
pipPossible = false
|
pipPossible = false
|
||||||
|
|
||||||
|
|
@ -312,7 +313,8 @@ final class VLCPlayerController: NSObject, ObservableObject, VLCMediaPlayerDeleg
|
||||||
manifestURL: url,
|
manifestURL: url,
|
||||||
keys: clearKeys,
|
keys: clearKeys,
|
||||||
preferHlsForAVPlayer: preferHls,
|
preferHlsForAVPlayer: preferHls,
|
||||||
browserCdnHeaders: browserCdnHeaders
|
browserCdnHeaders: browserCdnHeaders,
|
||||||
|
forceVod: forceVod
|
||||||
)
|
)
|
||||||
playURL = started.url
|
playURL = started.url
|
||||||
proxySession = started.session
|
proxySession = started.session
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ struct SettingsScreen: View {
|
||||||
|
|
||||||
Section("Info") {
|
Section("Info") {
|
||||||
LabeledContent("App", value: "VMC iOS")
|
LabeledContent("App", value: "VMC iOS")
|
||||||
LabeledContent("Versie", value: "0.5.58")
|
LabeledContent("Versie", value: "0.5.59")
|
||||||
LabeledContent("Live TV", value: app.iptvEnabled ? "Aan" : "Uit")
|
LabeledContent("Live TV", value: app.iptvEnabled ? "Aan" : "Uit")
|
||||||
LabeledContent("Events", value: app.eventsEnabled ? "Aan" : "Uit")
|
LabeledContent("Events", value: app.eventsEnabled ? "Aan" : "Uit")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ function hasKapWindow(url: string | null | undefined): boolean {
|
||||||
type StoredDrmPlay = {
|
type StoredDrmPlay = {
|
||||||
keys: Array<{ kid: string; key: string }>;
|
keys: Array<{ kid: string; key: string }>;
|
||||||
mpdUrl: string | null;
|
mpdUrl: string | null;
|
||||||
|
eventMatch?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function storedDrmPlay(
|
async function storedDrmPlay(
|
||||||
|
|
@ -407,16 +408,18 @@ async function storedXtraChannelPlay(
|
||||||
(a, b) => b[1].score - a[1].score || b[1].lastSeen - a[1].lastSeen
|
(a, b) => b[1].score - a[1].score || b[1].lastSeen - a[1].lastSeen
|
||||||
);
|
);
|
||||||
for (const [url, g] of ranked) {
|
for (const [url, g] of ranked) {
|
||||||
if (g.keys.length) return { keys: g.keys, mpdUrl: url };
|
if (g.keys.length) {
|
||||||
|
return { keys: g.keys, mpdUrl: url, eventMatch: g.score >= 200 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const salvage = preferLiveChannelUrl([
|
const salvage = preferLiveChannelUrl([
|
||||||
...xtraUrls,
|
...xtraUrls,
|
||||||
...(extraMpdUrls || []),
|
...(extraMpdUrls || []),
|
||||||
]);
|
]);
|
||||||
if (guidKeys.length && salvage && isXtraMpd(salvage)) {
|
if (guidKeys.length && salvage && isXtraMpd(salvage)) {
|
||||||
return { keys: guidKeys, mpdUrl: salvage };
|
return { keys: guidKeys, mpdUrl: salvage, eventMatch: true };
|
||||||
}
|
}
|
||||||
return { keys: [], mpdUrl: null };
|
return { keys: [], mpdUrl: null, eventMatch: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function dropEventMpdCache(mediaGuid: string, reason: string) {
|
async function dropEventMpdCache(mediaGuid: string, reason: string) {
|
||||||
|
|
@ -490,11 +493,75 @@ export async function ensureViaplayEventReady(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Terugkijken: KAP op de zender-MPD van dit event (xtra als die bij dit guid hoort).
|
||||||
|
if (ended) {
|
||||||
|
const xtra = await storedXtraChannelPlay(
|
||||||
|
sessionSecret,
|
||||||
|
mediaGuid,
|
||||||
|
event.title,
|
||||||
|
settings.config.events.map((e) => e.mpdUrl)
|
||||||
|
);
|
||||||
|
const baseRaw =
|
||||||
|
(xtra.eventMatch && xtra.mpdUrl) ||
|
||||||
|
preferLiveChannelUrl([stored.mpdUrl, event.mpdUrl, cachedMpd]) ||
|
||||||
|
stored.mpdUrl ||
|
||||||
|
event.mpdUrl ||
|
||||||
|
cachedMpd;
|
||||||
|
const base = baseRaw ? stripKapWindow(baseRaw) : null;
|
||||||
|
const keys = xtra.eventMatch && xtra.keys.length ? xtra.keys : cachedKeys;
|
||||||
|
if (base && keys.length && isReusableLiveMpd(base)) {
|
||||||
|
try {
|
||||||
|
const kap = await resolveKapMpd(
|
||||||
|
base,
|
||||||
|
event.startMs,
|
||||||
|
event.endMs,
|
||||||
|
Math.min(tweaks.httpTimeoutMs, 12_000)
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!isDontGoPlaceholder(kap.mpdUrl) &&
|
||||||
|
!(kap.xml && mpdLooksLikeDontGo(kap.xml))
|
||||||
|
) {
|
||||||
|
await patchViaplayEventCache(mediaGuid, {
|
||||||
|
mpdUrl: kap.mpdUrl,
|
||||||
|
mpdCachedAt: new Date().toISOString(),
|
||||||
|
hasPssh: kap.hasPssh,
|
||||||
|
hasKeys: true,
|
||||||
|
lastPlayError: null,
|
||||||
|
lastPlayAt: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
recordLivePlay({
|
||||||
|
guid: mediaGuid,
|
||||||
|
ok: true,
|
||||||
|
title: event.title,
|
||||||
|
mediaPath: mediaPathOf(kap.mpdUrl),
|
||||||
|
hasLicense: true,
|
||||||
|
fromCache: true,
|
||||||
|
cookies: emptyCookies(),
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
streamUrl: kap.mpdUrl,
|
||||||
|
format: "dash",
|
||||||
|
keys,
|
||||||
|
fromCache: true,
|
||||||
|
mediaGuid,
|
||||||
|
live: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* bestaande catch-up-paden hieronder */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Zelfde bron als de DRM-tabel in admin: opgeslagen MPD + KID/KEY.
|
// Zelfde bron als de DRM-tabel in admin: opgeslagen MPD + KID/KEY.
|
||||||
// Alleen opnieuw play-live als die set ontbreekt of Don't go is.
|
// Alleen opnieuw play-live als die set ontbreekt of Don't go is.
|
||||||
if (!opts?.forceRefresh && cachedKeys.length) {
|
if (!opts?.forceRefresh && cachedKeys.length) {
|
||||||
let url = ended
|
let url = ended
|
||||||
? stored.mpdUrl || event.mpdUrl || cachedMpd
|
? preferLiveChannelUrl([stored.mpdUrl, event.mpdUrl, cachedMpd]) ||
|
||||||
|
stored.mpdUrl ||
|
||||||
|
event.mpdUrl ||
|
||||||
|
cachedMpd
|
||||||
: preferLiveChannelUrl([event.mpdUrl, cachedMpd, stored.mpdUrl]);
|
: preferLiveChannelUrl([event.mpdUrl, cachedMpd, stored.mpdUrl]);
|
||||||
if (url && isReusableLiveMpd(url)) {
|
if (url && isReusableLiveMpd(url)) {
|
||||||
if (!ended) url = stripKapWindow(url);
|
if (!ended) url = stripKapWindow(url);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue