mirror of
https://github.com/dirtydishes/dreamio.git
synced 2026-06-06 13:37:24 +00:00
make caption menu state clearer
This commit is contained in:
parent
c0a017ceb2
commit
da7501d12b
6 changed files with 425 additions and 26 deletions
|
|
@ -12,3 +12,4 @@
|
|||
{"id":"int-6b806f87","kind":"field_change","created_at":"2026-05-25T09:49:39.908604Z","actor":"dirtydishes","issue_id":"dreamio-poo","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented and validated native player controls, subtitle handling refinements, and close-flow cleanup."}}
|
||||
{"id":"int-5d355e9b","kind":"field_change","created_at":"2026-05-25T09:51:17.04306Z","actor":"dirtydishes","issue_id":"dreamio-wgk","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Closed"}}
|
||||
{"id":"int-9ddb7b1a","kind":"field_change","created_at":"2026-05-25T10:18:30.826897Z","actor":"dirtydishes","issue_id":"dreamio-7w6","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Streamlined native player controls into a compact bottom overlay and validated the simulator build."}}
|
||||
{"id":"int-2a84633f","kind":"field_change","created_at":"2026-05-25T10:25:22.649574Z","actor":"dirtydishes","issue_id":"dreamio-88m","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented captions as a single-choice menu with None and selected loaded tracks, updated tests and turn documentation."}}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
{"_type":"issue","id":"dreamio-l68","title":"Add native playback for direct debrid streams","description":"Implement a WKWebView JavaScript bridge that detects direct-file debrid media URLs and routes unsupported containers to a native player backend, initially MobileVLCKit, while preserving normal Stremio Web playback for compatible streams.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T03:13:19Z","created_by":"dirtydishes","updated_at":"2026-05-25T03:20:17Z","started_at":"2026-05-25T03:13:28Z","closed_at":"2026-05-25T03:20:17Z","close_reason":"Implemented native direct-stream bridge, classification, MobileVLCKit backend wiring, CocoaPods workflow docs, and turn documentation. Full iOS build is blocked locally by missing CocoaPods and iPhoneOS SDK.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"dreamio-tnv","title":"Fix iOS bundle identifier install failure","description":"Xcode built Dreamio.app without a valid CFBundleIdentifier, causing device install to fail with CoreDeviceError 3000/3002. Investigate project bundle settings, fix the source configuration, validate the app bundle Info.plist, and document the change.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T01:23:00Z","created_by":"dirtydishes","updated_at":"2026-05-25T01:25:36Z","started_at":"2026-05-25T01:23:07Z","closed_at":"2026-05-25T01:25:36Z","close_reason":"Added bundle metadata to Info.plist and validated processed app bundle identifier.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"dreamio-4yn","title":"Build WKWebView MVP shell","description":"Create the first Dreamio MVP implementation: a minimal iOS WKWebView wrapper around hosted Stremio Web, with configuration, launch behavior, diagnostics, and documentation for real-device viability testing.","acceptance_criteria":"App project exists; WKWebView loads hosted Stremio Web; external/new-window navigation is handled; basic diagnostics and manual test documentation exist; quality gates are run or documented.","status":"closed","priority":1,"issue_type":"feature","owner":"dishes@dpdrm.com","created_at":"2026-05-24T14:55:12Z","created_by":"dirtydishes","updated_at":"2026-05-24T14:59:44Z","closed_at":"2026-05-24T14:59:44Z","close_reason":"Implemented the MVP WKWebView iOS shell, added run and validation documentation, and recorded current validation limits.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"dreamio-88m","title":"Make caption selection states clearer","description":"The native player caption menu should behave like a simple single-choice menu with None and loaded caption tracks, making the current caption state visually obvious.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T10:22:12Z","created_by":"dirtydishes","updated_at":"2026-05-25T10:25:23Z","started_at":"2026-05-25T10:22:48Z","closed_at":"2026-05-25T10:25:23Z","close_reason":"Implemented captions as a single-choice menu with None and selected loaded tracks, updated tests and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"dreamio-7w6","title":"Streamline native player controls","description":"Make the native playback controls take up less screen space while preserving play, seek, jump, captions, and close actions.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T10:15:49Z","created_by":"dirtydishes","updated_at":"2026-05-25T10:18:31Z","started_at":"2026-05-25T10:15:59Z","closed_at":"2026-05-25T10:18:31Z","close_reason":"Streamlined native player controls into a compact bottom overlay and validated the simulator build.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"dreamio-mj8","title":"Add native player controls and captions","description":"Implement a fuller VLC-backed native playback surface with transport controls, caption controls, external subtitle discovery, and a clean close flow back to Stremio episode selection.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T04:57:53Z","created_by":"dirtydishes","updated_at":"2026-05-25T05:04:55Z","started_at":"2026-05-25T04:57:57Z","closed_at":"2026-05-25T05:04:55Z","close_reason":"Implemented native VLC player controls, caption controls, subtitle candidate discovery, and close-flow cleanup.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"dreamio-evt","title":"Enable WebView inspection and playback diagnostics","description":"Add development-only WKWebView inspection and token-safe playback diagnostics so Dreamio can debug hosted Stremio media failures without changing app navigation, login, or playback behavior.","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T02:30:26Z","created_by":"dirtydishes","updated_at":"2026-05-25T02:34:55Z","started_at":"2026-05-25T02:30:32Z","closed_at":"2026-05-25T02:34:55Z","close_reason":"Implemented debug-only WKWebView inspection, token-safe playback diagnostics, navigation logging, validation build, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ final class NativePlayerViewController: UIViewController {
|
|||
playPauseButton.addTarget(self, action: #selector(togglePlayPause), for: .touchUpInside)
|
||||
jumpBackButton.addTarget(self, action: #selector(jumpBack), for: .touchUpInside)
|
||||
jumpForwardButton.addTarget(self, action: #selector(jumpForward), for: .touchUpInside)
|
||||
captionsButton.addTarget(self, action: #selector(showCaptions), for: .touchUpInside)
|
||||
captionsButton.showsMenuAsPrimaryAction = true
|
||||
playPauseButton.layer.cornerRadius = 21
|
||||
scrubber.addTarget(self, action: #selector(scrubbingStarted), for: .touchDown)
|
||||
scrubber.addTarget(self, action: #selector(scrubberChanged), for: .valueChanged)
|
||||
|
|
@ -314,28 +314,38 @@ final class NativePlayerViewController: UIViewController {
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func showCaptions() {
|
||||
revealControls()
|
||||
let alert = UIAlertController(title: "Captions", message: nil, preferredStyle: .actionSheet)
|
||||
SubtitleOptionMapper.options(from: backend.subtitleTracks).forEach { track in
|
||||
let prefix = track.id == backend.selectedSubtitleTrackID ? "Selected: " : ""
|
||||
alert.addAction(UIAlertAction(title: "\(prefix)\(track.name)", style: .default) { [weak self] _ in
|
||||
private func captionsMenu() -> UIMenu {
|
||||
let selectedTrackID = backend.selectedSubtitleTrackID
|
||||
let trackActions = SubtitleOptionMapper.options(from: backend.subtitleTracks).map { track in
|
||||
UIAction(
|
||||
title: track.name,
|
||||
state: track.id == selectedTrackID ? .on : .off
|
||||
) { [weak self] _ in
|
||||
self?.backend.selectSubtitleTrack(id: track.id)
|
||||
})
|
||||
self?.refreshControls()
|
||||
}
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "Delay -0.5s", style: .default) { [weak self] _ in
|
||||
self?.backend.adjustSubtitleDelay(by: -0.5)
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Delay +0.5s", style: .default) { [weak self] _ in
|
||||
self?.backend.adjustSubtitleDelay(by: 0.5)
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Current Delay: \(String(format: "%.1fs", backend.subtitleDelay))", style: .default))
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
if let popover = alert.popoverPresentationController {
|
||||
popover.sourceView = captionsButton
|
||||
popover.sourceRect = captionsButton.bounds
|
||||
}
|
||||
present(alert, animated: true)
|
||||
|
||||
let delayActions = UIMenu(
|
||||
title: "Delay",
|
||||
options: .displayInline,
|
||||
children: [
|
||||
UIAction(title: "Decrease 0.5s") { [weak self] _ in
|
||||
self?.backend.adjustSubtitleDelay(by: -0.5)
|
||||
self?.refreshControls()
|
||||
},
|
||||
UIAction(title: "Increase 0.5s") { [weak self] _ in
|
||||
self?.backend.adjustSubtitleDelay(by: 0.5)
|
||||
self?.refreshControls()
|
||||
},
|
||||
UIAction(
|
||||
title: "Current: \(String(format: "%.1fs", backend.subtitleDelay))",
|
||||
attributes: .disabled
|
||||
) { _ in }
|
||||
]
|
||||
)
|
||||
|
||||
return UIMenu(title: "Captions", children: trackActions + [delayActions])
|
||||
}
|
||||
|
||||
private func startProgressUpdates() {
|
||||
|
|
@ -351,6 +361,7 @@ final class NativePlayerViewController: UIViewController {
|
|||
jumpBackButton.isEnabled = backend.isSeekable
|
||||
jumpForwardButton.isEnabled = backend.isSeekable
|
||||
captionsButton.isEnabled = !SubtitleOptionMapper.options(from: backend.subtitleTracks).isEmpty
|
||||
captionsButton.menu = captionsMenu()
|
||||
elapsedLabel.text = PlaybackTimeFormatter.label(for: backend.currentTime)
|
||||
remainingLabel.text = "-\(PlaybackTimeFormatter.label(for: backend.remainingTime))"
|
||||
if !isScrubbing {
|
||||
|
|
|
|||
|
|
@ -59,10 +59,10 @@ enum PlaybackTimeFormatter {
|
|||
}
|
||||
|
||||
enum SubtitleOptionMapper {
|
||||
static let offTrack = SubtitleTrack(id: -1, name: "Off")
|
||||
static let noneTrack = SubtitleTrack(id: -1, name: "None")
|
||||
|
||||
static func options(from tracks: [SubtitleTrack]) -> [SubtitleTrack] {
|
||||
[offTrack] + tracks.filter { $0.id >= 0 }
|
||||
[noneTrack] + tracks.filter { $0.id >= 0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ struct StreamResolverTests {
|
|||
testRedactorHandlesPercentEncodedPath()
|
||||
testPlaybackTimeFormatting()
|
||||
testSubtitleCandidateParsing()
|
||||
testSubtitleOptionMappingIncludesOff()
|
||||
testSubtitleOptionMappingIncludesNone()
|
||||
print("StreamResolverTests passed")
|
||||
}
|
||||
|
||||
|
|
@ -110,13 +110,13 @@ struct StreamResolverTests {
|
|||
assertEqual(candidates[2].url.absoluteString, "https://cdn.example.test/movie.fr.ass?download=1")
|
||||
}
|
||||
|
||||
private static func testSubtitleOptionMappingIncludesOff() {
|
||||
private static func testSubtitleOptionMappingIncludesNone() {
|
||||
let options = SubtitleOptionMapper.options(from: [
|
||||
SubtitleTrack(id: 2, name: "English"),
|
||||
SubtitleTrack(id: 5, name: "Spanish")
|
||||
])
|
||||
|
||||
assertEqual(options.map(\.name), ["Off", "English", "Spanish"])
|
||||
assertEqual(options.map(\.name), ["None", "English", "Spanish"])
|
||||
assertEqual(options.first?.id, -1)
|
||||
}
|
||||
|
||||
|
|
|
|||
386
docs/turns/2026-05-25-caption-menu-selection-state.html
Normal file
386
docs/turns/2026-05-25-caption-menu-selection-state.html
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue