mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-03 16:41:26 +01:00
feat(core,app,android,ios,cli): look up new titles after every crawl, show presses and work on iOS
Metadata was only fetched after adding a share (mobile) or from the Settings buttons (desktop), so a show or episode that turned up in an existing share stayed bare. Every crawl is now followed by a match run when enrichment is on; `plan` makes no requests for a library that is already matched. A stored episode guide is fetched again when a file newer than it has no named entry, and daily for two weeks while an aired episode is still unnamed. Removing a share now carries its watch state to any other share holding the same file, `pstr remove` drops the share's catalog rows, and the app forgets catalog rows of shares no longer configured — an older `pstr remove` left them, and every episode showed twice. iOS, for a slow iPad: - swipe back on a settings page uses its own edge strip; on the whole page it also pressed the row under the finger and opened the metadata sheet - "Change match" from the tile menu opens once the menu has dismissed, not after a guessed 350 ms that a slow device missed - the activity banner tracks each running line by itself (also Android), so overlapping work no longer leaves it spinning forever - buttons, tiles and rows draw their press; marking watched shows at once; remove/repair share, sign out and deletions show the banner; the player says "Opening…" while the stream opens - posters are read into a reserved buffer instead of a byte at a time
This commit is contained in:
1 parent
2f3be1d193
commit
73973841c2
19 files changed
+692
-113
No files matched your search
@@ -77,6 +77,10 @@ final class PlayerHost {
|
||||
private(set) var chapters = ChapterPlan(entries: [], creditsStart: nil)
|
||||
/// What stopped this episode, until the viewer dismisses it.
|
||||
var playbackError: String?
|
||||
/// The stream is being opened: the share session, the revision and its
|
||||
/// block list, all before mpv has a byte. On an old iPad that is seconds
|
||||
/// of a black screen unless something says so.
|
||||
private(set) var opening = false
|
||||
/// Read once per episode: a preference the viewer changes mid-episode
|
||||
/// applies to the next one.
|
||||
private(set) var autoSkip = false
|
||||
@@ -177,6 +181,7 @@ final class PlayerHost {
|
||||
index = 0
|
||||
minimized = false
|
||||
playbackError = nil
|
||||
opening = false
|
||||
chapters = ChapterPlan(entries: [], creditsStart: nil)
|
||||
OrientationLock.set(false)
|
||||
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
||||
@@ -210,6 +215,7 @@ final class PlayerHost {
|
||||
return
|
||||
}
|
||||
try? AVAudioSession.sharedInstance().setActive(true)
|
||||
opening = true
|
||||
loadTask = Task {
|
||||
do {
|
||||
try await session.open()
|
||||
@@ -244,6 +250,8 @@ final class PlayerHost {
|
||||
hardwareDecoding: SettingsStore().hardwareDecoding
|
||||
)
|
||||
guard loaded else { throw PlaybackFailure("libmpv rejected the stream") }
|
||||
// mpv's own buffering takes over the spinner from here.
|
||||
opening = false
|
||||
session.transferToPlayer()
|
||||
// After the load, because mpv keeps none of the three across one.
|
||||
player.setVolume(global.volume)
|
||||
@@ -256,6 +264,7 @@ final class PlayerHost {
|
||||
abandon(session)
|
||||
return
|
||||
}
|
||||
opening = false
|
||||
playbackError = errorMessage(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +73,12 @@ struct PlayerView: View {
|
||||
|
||||
// Only while nothing else explains the still picture: an error
|
||||
// is the more useful thing to say.
|
||||
if player.stalled && host.playbackError == nil {
|
||||
if (host.opening || player.stalled) && host.playbackError == nil {
|
||||
VStack(spacing: 12) {
|
||||
ProgressView().controlSize(.large).tint(scheme.primary)
|
||||
if player.buffering {
|
||||
if host.opening {
|
||||
Text("Opening…").textStyle(.labelMedium).foregroundStyle(scheme.onSurface)
|
||||
} else if player.buffering {
|
||||
Text(player.cachePercent > 0 ? "Buffering… \(Int(player.cachePercent.rounded()))%" : "Buffering…")
|
||||
.textStyle(.labelMedium).foregroundStyle(scheme.onSurface)
|
||||
}
|
||||
@@ -295,7 +297,7 @@ private struct CompactIcon: View {
|
||||
.frame(width: 40, height: 40)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.buttonStyle(PressableStyle(circle: true, wash: .white))
|
||||
.disabled(!enabled)
|
||||
.accessibilityLabel(label)
|
||||
}
|
||||
@@ -342,7 +344,7 @@ private struct UpNextCard: View {
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: Corner.standard))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.buttonStyle(PressableStyle(wash: .white))
|
||||
.accessibilityLabel("Play now")
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Up next in \(remaining)s").textStyle(.labelMedium).foregroundStyle(scheme.onSurfaceVariant)
|
||||
@@ -515,7 +517,7 @@ private struct EpisodeSheet: View {
|
||||
.background(current ? scheme.secondaryContainer : .clear)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.buttonStyle(.pressable)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user