feat: support Miruro provider tracks, proxies, and native embeds
This commit is contained in:
@@ -125,14 +125,17 @@ final class PlayerModel: ObservableObject {
|
||||
@Published private(set) var miruroCaptionProvider: String
|
||||
@Published private(set) var selectedMiruroVideoProvider: String?
|
||||
@Published private(set) var selectedMiruroCaptionProvider: String?
|
||||
private var pendingMiruro: (mediaID: String, episode: Int, sources: MiruroSources)?
|
||||
private var miruroSources: MiruroSources? {
|
||||
if let pendingMiruro { return pendingMiruro.sources }
|
||||
if case .resolved(_, let providers) = currentMedia { return providers }
|
||||
return nil
|
||||
}
|
||||
var miruroVideoProviders: [String] { miruroSources?.videoProviders ?? [] }
|
||||
var miruroCaptionProviders: [String] { miruroSources?.captionProviders ?? [] }
|
||||
var canChangeMiruroProvider: Bool {
|
||||
castDevice == nil && miruroSources != nil && canTogglePlayback &&
|
||||
castDevice == nil && miruroSources != nil &&
|
||||
(pendingMiruro != nil ? canOpen && !resolvingStream : canTogglePlayback) &&
|
||||
!isSeeking && !transition.seekPending && !state.seek_pending
|
||||
}
|
||||
@Published private(set) var subtitleTracks: [ResolvedSubtitle] = []
|
||||
@@ -421,8 +424,9 @@ final class PlayerModel: ObservableObject {
|
||||
source = .youtube(video, height: nil)
|
||||
case .miruro(let mediaID, let episode):
|
||||
let providers = try await miruroResolver(mediaID, episode)
|
||||
let resolved = try providers.select(videoProvider: miruroVideoProvider,
|
||||
let selected = try providers.select(videoProvider: miruroVideoProvider,
|
||||
captionProvider: miruroCaptionProvider).source
|
||||
let resolved = try await MiruroTransport.prepare(selected)
|
||||
guard resolved.identity == "miruro:\(mediaID):\(episode)" else { throw CocoaError(.fileReadCorruptFile) }
|
||||
url = URL(string: text.trimmingCharacters(in: .whitespacesAndNewlines))!
|
||||
title = resolved.title
|
||||
@@ -551,7 +555,8 @@ final class PlayerModel: ObservableObject {
|
||||
let track = subtitleTracks[index]
|
||||
subtitleTask = Task { [weak self] in
|
||||
do {
|
||||
let document = try await WebVTTDocument.load(url: track.url, headers: track.headers)
|
||||
let prepared = try await MiruroTransport.subtitle(track)
|
||||
let document = try await WebVTTDocument.load(url: prepared.url, headers: prepared.headers)
|
||||
guard !Task.isCancelled, let self else { return }
|
||||
self.subtitleDocument = document
|
||||
self.updateSubtitle()
|
||||
@@ -598,8 +603,13 @@ final class PlayerModel: ObservableObject {
|
||||
|
||||
func setMiruroVideoProvider(_ provider: String) {
|
||||
guard canChangeMiruroProvider, let handle,
|
||||
provider.isEmpty || miruroVideoProviders.contains(provider),
|
||||
provider != miruroVideoProvider,
|
||||
provider.isEmpty || miruroVideoProviders.contains(provider) else { return }
|
||||
if let pendingMiruro {
|
||||
miruroVideoProvider = provider
|
||||
resolveMiruro(mediaID: pendingMiruro.mediaID, episode: pendingMiruro.episode)
|
||||
return
|
||||
}
|
||||
guard provider != miruroVideoProvider,
|
||||
case .resolved(let source, _) = currentMedia else { return }
|
||||
let identity = source.identity.split(separator: ":")
|
||||
guard identity.count == 3, let episode = Int(identity[2]) else { return }
|
||||
@@ -611,13 +621,18 @@ final class PlayerModel: ObservableObject {
|
||||
func setMiruroCaptionProvider(_ provider: String) {
|
||||
guard canChangeMiruroProvider, let providers = miruroSources,
|
||||
provider.isEmpty || miruroCaptionProviders.contains(provider) else { return }
|
||||
if pendingMiruro != nil {
|
||||
miruroCaptionProvider = provider
|
||||
return
|
||||
}
|
||||
guard case .resolved(let current, _) = currentMedia else { return }
|
||||
do {
|
||||
let selection = try providers.select(videoProvider: selectedMiruroVideoProvider,
|
||||
captionProvider: provider)
|
||||
let language = selectedSubtitle.flatMap { subtitleTracks.indices.contains($0) ? subtitleTracks[$0].language : nil }
|
||||
miruroCaptionProvider = provider
|
||||
selectedMiruroCaptionProvider = selection.captionProvider
|
||||
currentMedia = .resolved(selection.source, providers: providers)
|
||||
currentMedia = .resolved(current.replacingSubtitles(selection.source.subtitles), providers: providers)
|
||||
subtitleTracks = selection.source.subtitles
|
||||
let index = language.flatMap { language in subtitleTracks.firstIndex { $0.language == language } ??
|
||||
subtitleTracks.firstIndex(where: \.isDefault) }
|
||||
@@ -647,10 +662,12 @@ final class PlayerModel: ObservableObject {
|
||||
let videoProvider = miruroVideoProvider
|
||||
let captionProvider = miruroCaptionProvider
|
||||
let task = Task { [weak self] in
|
||||
var catalog: MiruroSources?
|
||||
do {
|
||||
let providers = try await resolver(mediaID, episode)
|
||||
catalog = providers
|
||||
let selection = try providers.select(videoProvider: videoProvider, captionProvider: captionProvider)
|
||||
let source = selection.source
|
||||
let source = try await MiruroTransport.prepare(selection.source)
|
||||
guard source.identity == "miruro:\(mediaID):\(episode)" else {
|
||||
throw CocoaError(.fileReadCorruptFile)
|
||||
}
|
||||
@@ -662,6 +679,9 @@ final class PlayerModel: ObservableObject {
|
||||
guard !Task.isCancelled, let self else { return }
|
||||
self.objectWillChange.send()
|
||||
guard self.transition.finishResolution(request) != nil else { return }
|
||||
if let catalog, self.currentMedia?.url.absoluteString != "miruro:\(mediaID):\(episode)" {
|
||||
self.pendingMiruro = (mediaID, episode, catalog)
|
||||
}
|
||||
self.failStreamChange(change, error: error)
|
||||
}
|
||||
}
|
||||
@@ -712,6 +732,10 @@ final class PlayerModel: ObservableObject {
|
||||
}
|
||||
|
||||
private func cancelStreamResolution() {
|
||||
if pendingMiruro != nil {
|
||||
objectWillChange.send()
|
||||
pendingMiruro = nil
|
||||
}
|
||||
guard transition.isResolving else { return }
|
||||
objectWillChange.send()
|
||||
transition.cancelResolution()
|
||||
@@ -872,6 +896,7 @@ final class PlayerModel: ObservableObject {
|
||||
queuePlaybackRevision = queue.currentID == nil ? nil : player_get_state(handle).revision
|
||||
queueCompletionHandled = false
|
||||
currentMedia = source
|
||||
pendingMiruro = nil
|
||||
if case .resolved(_, let providers) = source,
|
||||
let selection = try? providers?.select(videoProvider: miruroVideoProvider,
|
||||
captionProvider: miruroCaptionProvider) {
|
||||
|
||||
Reference in New Issue
Block a user