mirror of
https://github.com/yattee/yattee.git
synced 2026-10-27 23:17:18 +00:00
Phase 1 of the player panels redesign: - New PanelSettings model (pages, regions, layout mode, default visibility) stored as an optional field on PlayerControlsLayout with an effectivePanelSettings accessor; lenient Codable so future page types and settings payloads never corrupt saved presets. - New Views/Player/Panels content engine: PlayerPanelContentView renders a page bar plus the selected page (info, comments, related, chapters, queue, tracks), with runtime hiding of empty pages. - PortraitDetailsPanel and FloatingDetailsPanel now host the shared content instead of their duplicated info bodies; drag machinery, pills, and fades are untouched. - Comments list extracted into CommentsListContent shared by the overlay and the comments page; comments pill routes to the comments page (expanding the portrait panel) when the page is enabled, and falls back to the overlay when it is not. - Tracks page embeds QualitySelectorView via a new isEmbedded mode. - Deleted the unused video info bodies in ExpandedPlayerSheet+VideoInfo and the dead PlayerInfoTab model.
95 lines
4.0 KiB
Swift
95 lines
4.0 KiB
Swift
//
|
|
// TracksPanelPage.swift
|
|
// Yattee
|
|
//
|
|
// Details panel page embedding the quality/audio/subtitles/rate selector.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
#if os(iOS) || os(macOS)
|
|
|
|
struct TracksPanelPage: View {
|
|
let context: PanelPageContext
|
|
var bottomInset: CGFloat = 0
|
|
|
|
private var appEnvironment: AppEnvironment? { context.appEnvironment }
|
|
private var playerService: PlayerService? { appEnvironment?.playerService }
|
|
private var playerState: PlayerState? { playerService?.state }
|
|
|
|
var body: some View {
|
|
if let playerService {
|
|
let supportedFormats = playerService.currentBackendType.supportedFormats
|
|
QualitySelectorView(
|
|
streams: playerService.availableStreams.filter { stream in
|
|
let format = StreamFormat.detect(from: stream)
|
|
// DASH is never offered for manual selection
|
|
if format == .dash {
|
|
return false
|
|
}
|
|
return supportedFormats.contains(format)
|
|
},
|
|
captions: playerService.availableCaptions,
|
|
currentStream: playerState?.currentStream,
|
|
currentAudioStream: playerState?.currentAudioStream,
|
|
currentCaption: playerService.currentCaption,
|
|
isLoading: playerService.availableStreams.isEmpty && playerState?.playbackState == .loading,
|
|
currentDownload: playerService.currentDownload,
|
|
isLoadingOnlineStreams: playerService.isLoadingOnlineStreams,
|
|
localCaptionURL: playerService.currentDownload.flatMap { download in
|
|
guard let path = download.localCaptionPath else { return nil }
|
|
return appEnvironment?.downloadManager.downloadsDirectory().appendingPathComponent(path)
|
|
},
|
|
currentRate: playerState?.rate ?? .x1,
|
|
isControlsLocked: playerState?.isControlsLocked ?? false,
|
|
isAudioMode: appEnvironment?.settingsManager.audioOnlyModeEnabled ?? false,
|
|
embeddedAudioTracks: playerService.embeddedAudioTracks,
|
|
embeddedSubtitleTracks: playerService.embeddedSubtitleTracks,
|
|
currentEmbeddedAudioTrackID: playerService.selectedEmbeddedAudioTrackID,
|
|
currentEmbeddedSubtitleTrackID: playerService.selectedEmbeddedSubtitleTrackID,
|
|
embeddedVideoTrack: playerService.primaryEmbeddedVideoTrack,
|
|
onStreamSelected: { stream, audioStream in
|
|
Task {
|
|
await playerService.selectStreamManually(stream, audioStream: audioStream)
|
|
}
|
|
},
|
|
onCaptionSelected: { caption in
|
|
playerService.loadCaption(caption)
|
|
},
|
|
onLoadOnlineStreams: {
|
|
Task {
|
|
await playerService.loadOnlineStreams()
|
|
}
|
|
},
|
|
onSwitchToOnlineStream: { stream, audioStream in
|
|
Task {
|
|
await playerService.switchToOnlineStream(stream, audioStream: audioStream)
|
|
}
|
|
},
|
|
onEmbeddedAudioTrackSelected: { trackID in
|
|
playerService.selectEmbeddedAudioTrack(trackID)
|
|
},
|
|
onEmbeddedSubtitleTrackSelected: { trackID in
|
|
playerService.selectEmbeddedSubtitleTrack(trackID)
|
|
},
|
|
onRateChanged: { rate in
|
|
playerState?.rate = rate
|
|
playerService.currentBackend?.rate = Float(rate.rawValue)
|
|
},
|
|
onLockToggled: { locked in
|
|
playerState?.isControlsLocked = locked
|
|
},
|
|
onAudioModeToggled: { enabled in
|
|
Task {
|
|
await playerService.setAudioMode(enabled)
|
|
}
|
|
},
|
|
isEmbedded: true
|
|
)
|
|
.safeAreaPadding(.bottom, bottomInset)
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|