mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-03 16:41:26 +01:00
feat(core,app,android,ios): play the dialogue, not the signs and songs
Anime releases often carry two subtitle tracks in one language, the dialogue and a "Signs & Songs" track, and the second is as often as not the one marked default or forced. mpv, picking by language, played it. pstr_core::subtitles reads a track's name and forced flag for whether it is the dialogue, and every client asks it once per file, after mpv has made its own pick, whether to switch to a track in the same language that is. A subtitle track the viewer chose by name stays, and subtitles that are off stay off.
This commit is contained in:
1 parent
8d615579e2
commit
b1ea076ea3
10 files changed
+353
-7
No files matched your search
@@ -33,6 +33,10 @@ struct MpvTrack: Identifiable, Hashable {
|
||||
let language: String
|
||||
let title: String
|
||||
let selected: Bool
|
||||
/// The container marks it as the default choice.
|
||||
var isDefault = false
|
||||
/// The container marks it as forced: on-screen text only, by convention.
|
||||
var forced = false
|
||||
|
||||
var label: String {
|
||||
let parts = [language, title].filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
|
||||
@@ -91,6 +95,8 @@ final class MpvPlayer {
|
||||
/// Called when the duration or the pause state changes: what the lock
|
||||
/// screen shows, and whether the screen may sleep.
|
||||
@ObservationIgnored var onStateChanged: (() -> Void)?
|
||||
/// Called once per file, when mpv has loaded it and picked its tracks.
|
||||
@ObservationIgnored var onLoaded: (() -> Void)?
|
||||
|
||||
@ObservationIgnored let layer = MpvMetalLayer()
|
||||
@ObservationIgnored private var mpv: OpaquePointer?
|
||||
@@ -473,6 +479,7 @@ final class MpvPlayer {
|
||||
let ready = pendingSubtitles
|
||||
pendingSubtitles = []
|
||||
attach(ready)
|
||||
onLoaded?()
|
||||
}
|
||||
case .restarted:
|
||||
seeking = false
|
||||
@@ -511,7 +518,9 @@ final class MpvPlayer {
|
||||
type: getString(base + "type") ?? "",
|
||||
language: getString(base + "lang") ?? "",
|
||||
title: getString(base + "title") ?? "",
|
||||
selected: getString(base + "selected") == "yes"
|
||||
selected: getString(base + "selected") == "yes",
|
||||
isDefault: getString(base + "default") == "yes",
|
||||
forced: getString(base + "forced") == "yes"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,6 +256,7 @@ final class PlayerHost {
|
||||
player?.onEnded = { [weak self] reason, media in self?.ended(reason, media) }
|
||||
player?.onTick = { [weak self] in self?.tick() }
|
||||
player?.onStateChanged = { [weak self] in self?.publishNowPlaying() }
|
||||
player?.onLoaded = { [weak self] in self?.preferFullSubtitles() }
|
||||
}
|
||||
guard let player else {
|
||||
playbackError = "Playback is unavailable: libmpv did not start."
|
||||
@@ -433,6 +434,18 @@ final class PlayerHost {
|
||||
chapters = chapterPlan(chapters: player.chapters, duration: player.duration > 0 ? player.duration : nil)
|
||||
}
|
||||
|
||||
/// Play the dialogue rather than the signs and songs mpv picked by
|
||||
/// language (`pstr_core::subtitles`). Not remembered: it is the choice mpv
|
||||
/// should have made, not the viewer's.
|
||||
private func preferFullSubtitles() {
|
||||
guard let player, playingOpenEpisode else { return }
|
||||
let subtitles = player.tracks.filter { $0.type == "sub" }.map {
|
||||
SubtitleTrackRecord(id: $0.id, language: $0.language, title: $0.title, default: $0.isDefault, forced: $0.forced, selected: $0.selected)
|
||||
}
|
||||
guard let id = fullSubtitlesInstead(tracks: subtitles), let track = player.tracks.first(where: { $0.type == "sub" && $0.id == id }) else { return }
|
||||
player.select(type: "sub", track: track)
|
||||
}
|
||||
|
||||
/// The skip button's offer at the current position, if any.
|
||||
var offer: SkipOffer? {
|
||||
guard let player, playingOpenEpisode else { return nil }
|
||||
|
||||
Reference in new issue
Block a user