mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Show stream opening status with AVPlayer
This commit is contained in:
parent
2b7ccc4b03
commit
c6798be167
@ -38,9 +38,7 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
!avPlayer.currentItem.isNil
|
!avPlayer.currentItem.isNil
|
||||||
}
|
}
|
||||||
|
|
||||||
var isLoadingVideo: Bool {
|
var isLoadingVideo = false
|
||||||
model.currentItem == nil || model.time == nil || !model.time!.isValid
|
|
||||||
}
|
|
||||||
|
|
||||||
var isPlaying: Bool {
|
var isPlaying: Bool {
|
||||||
avPlayer.timeControlStatus == .playing
|
avPlayer.timeControlStatus == .playing
|
||||||
@ -138,6 +136,8 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
preservingTime: Bool,
|
preservingTime: Bool,
|
||||||
upgrading _: Bool
|
upgrading _: Bool
|
||||||
) {
|
) {
|
||||||
|
isLoadingVideo = true
|
||||||
|
|
||||||
if let url = stream.singleAssetURL {
|
if let url = stream.singleAssetURL {
|
||||||
model.logger.info("playing stream with one asset\(stream.kind == .hls ? " (HLS)" : ""): \(url)")
|
model.logger.info("playing stream with one asset\(stream.kind == .hls ? " (HLS)" : ""): \(url)")
|
||||||
|
|
||||||
@ -475,6 +475,8 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isLoadingVideo = false
|
||||||
|
|
||||||
switch playerItem.status {
|
switch playerItem.status {
|
||||||
case .readyToPlay:
|
case .readyToPlay:
|
||||||
if self.model.activeBackend == .appleAVPlayer,
|
if self.model.activeBackend == .appleAVPlayer,
|
||||||
|
@ -32,6 +32,17 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var playerItemEndTimeWithSegments: CMTime? {
|
||||||
|
if let duration = playerItemDuration,
|
||||||
|
let segment = sponsorBlock.segments.last,
|
||||||
|
segment.endTime.seconds >= duration.seconds - 3
|
||||||
|
{
|
||||||
|
return segment.endTime
|
||||||
|
}
|
||||||
|
|
||||||
|
return playerItemDuration
|
||||||
|
}
|
||||||
|
|
||||||
private func skip(_ segment: Segment, at time: CMTime) {
|
private func skip(_ segment: Segment, at time: CMTime) {
|
||||||
if let duration = playerItemDuration, segment.endTime.seconds >= duration.seconds - 3 {
|
if let duration = playerItemDuration, segment.endTime.seconds >= duration.seconds - 3 {
|
||||||
logger.error("segment end time is: \(segment.end) when player item duration is: \(duration.seconds)")
|
logger.error("segment end time is: \(segment.end) when player item duration is: \(duration.seconds)")
|
||||||
|
@ -42,6 +42,8 @@ struct PlayerControls: View {
|
|||||||
@Default(.playerControlsPlaybackModeEnabled) private var playerControlsPlaybackModeEnabled
|
@Default(.playerControlsPlaybackModeEnabled) private var playerControlsPlaybackModeEnabled
|
||||||
@Default(.playerControlsMusicModeEnabled) private var playerControlsMusicModeEnabled
|
@Default(.playerControlsMusicModeEnabled) private var playerControlsMusicModeEnabled
|
||||||
|
|
||||||
|
@Default(.avPlayerUsesSystemControls) private var avPlayerUsesSystemControls
|
||||||
|
|
||||||
private let controlsOverlayModel = ControlOverlaysModel.shared
|
private let controlsOverlayModel = ControlOverlaysModel.shared
|
||||||
private var navigation = NavigationModel.shared
|
private var navigation = NavigationModel.shared
|
||||||
|
|
||||||
@ -49,22 +51,28 @@ struct PlayerControls: View {
|
|||||||
player.playingFullScreen ? fullScreenPlayerControlsLayout : regularPlayerControlsLayout
|
player.playingFullScreen ? fullScreenPlayerControlsLayout : regularPlayerControlsLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showControls: Bool {
|
||||||
|
player.activeBackend == .mpv || !avPlayerUsesSystemControls
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
Seek()
|
if showControls {
|
||||||
.zIndex(4)
|
Seek()
|
||||||
.transition(.opacity)
|
.zIndex(4)
|
||||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
.transition(.opacity)
|
||||||
#if os(tvOS)
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||||
.focused($focusedField, equals: .seekOSD)
|
#if os(tvOS)
|
||||||
.onChange(of: player.seek.lastSeekTime) { _ in
|
.focused($focusedField, equals: .seekOSD)
|
||||||
if !model.presentingControls {
|
.onChange(of: player.seek.lastSeekTime) { _ in
|
||||||
focusedField = .seekOSD
|
if !model.presentingControls {
|
||||||
|
focusedField = .seekOSD
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
#else
|
||||||
#else
|
.offset(y: 2)
|
||||||
.offset(y: 2)
|
#endif
|
||||||
#endif
|
}
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
ZStack {
|
ZStack {
|
||||||
@ -78,106 +86,108 @@ struct PlayerControls: View {
|
|||||||
}
|
}
|
||||||
.offset(y: playerControlsLayout.osdVerticalOffset + 5)
|
.offset(y: playerControlsLayout.osdVerticalOffset + 5)
|
||||||
|
|
||||||
Section {
|
if showControls {
|
||||||
#if !os(tvOS)
|
Section {
|
||||||
HStack {
|
#if !os(tvOS)
|
||||||
seekBackwardButton
|
HStack {
|
||||||
Spacer()
|
|
||||||
togglePlayButton
|
|
||||||
Spacer()
|
|
||||||
seekForwardButton
|
|
||||||
}
|
|
||||||
.font(.system(size: playerControlsLayout.bigButtonFontSize))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ZStack(alignment: .bottom) {
|
|
||||||
VStack(spacing: 4) {
|
|
||||||
#if !os(tvOS)
|
|
||||||
buttonsBar
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
if !player.currentVideo.isNil, player.playingFullScreen {
|
|
||||||
Button {
|
|
||||||
withAnimation(Self.animation) {
|
|
||||||
model.presentingDetailsOverlay = true
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
ControlsBar(fullScreen: $model.presentingDetailsOverlay, expansionState: .constant(.full), presentingControls: false, detailsTogglePlayer: false, detailsToggleFullScreen: false)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
|
||||||
.frame(maxWidth: 300, alignment: .leading)
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
if playerControlsLayout.displaysTitleLine {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text(player.videoForDisplay?.displayTitle ?? "Not Playing")
|
|
||||||
.shadow(radius: 10)
|
|
||||||
.font(.system(size: playerControlsLayout.titleLineFontSize).bold())
|
|
||||||
.lineLimit(1)
|
|
||||||
|
|
||||||
Text(player.currentVideo?.displayAuthor ?? "")
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.shadow(radius: 10)
|
|
||||||
.foregroundColor(.init(white: 0.8))
|
|
||||||
.font(.system(size: playerControlsLayout.authorLineFontSize))
|
|
||||||
.lineLimit(1)
|
|
||||||
}
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.offset(y: -40)
|
|
||||||
}
|
|
||||||
|
|
||||||
timeline
|
|
||||||
.padding(.bottom, 2)
|
|
||||||
}
|
|
||||||
.zIndex(1)
|
|
||||||
.padding(.top, 2)
|
|
||||||
.transition(.opacity)
|
|
||||||
|
|
||||||
HStack(spacing: playerControlsLayout.buttonsSpacing) {
|
|
||||||
#if os(tvOS)
|
|
||||||
togglePlayButton
|
|
||||||
seekBackwardButton
|
seekBackwardButton
|
||||||
|
Spacer()
|
||||||
|
togglePlayButton
|
||||||
|
Spacer()
|
||||||
seekForwardButton
|
seekForwardButton
|
||||||
#endif
|
|
||||||
if playerControlsRestartEnabled {
|
|
||||||
restartVideoButton
|
|
||||||
}
|
}
|
||||||
if playerControlsAdvanceToNextEnabled {
|
.font(.system(size: playerControlsLayout.bigButtonFontSize))
|
||||||
advanceToNextItemButton
|
#endif
|
||||||
}
|
|
||||||
Spacer()
|
ZStack(alignment: .bottom) {
|
||||||
#if os(tvOS)
|
VStack(spacing: 4) {
|
||||||
if playerControlsSettingsEnabled {
|
#if !os(tvOS)
|
||||||
settingsButton
|
buttonsBar
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
if !player.currentVideo.isNil, player.playingFullScreen {
|
||||||
|
Button {
|
||||||
|
withAnimation(Self.animation) {
|
||||||
|
model.presentingDetailsOverlay = true
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
ControlsBar(fullScreen: $model.presentingDetailsOverlay, expansionState: .constant(.full), presentingControls: false, detailsTogglePlayer: false, detailsToggleFullScreen: false)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||||
|
.frame(maxWidth: 300, alignment: .leading)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if playerControlsLayout.displaysTitleLine {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(player.videoForDisplay?.displayTitle ?? "Not Playing")
|
||||||
|
.shadow(radius: 10)
|
||||||
|
.font(.system(size: playerControlsLayout.titleLineFontSize).bold())
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
|
Text(player.currentVideo?.displayAuthor ?? "")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.shadow(radius: 10)
|
||||||
|
.foregroundColor(.init(white: 0.8))
|
||||||
|
.font(.system(size: playerControlsLayout.authorLineFontSize))
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.offset(y: -40)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if playerControlsPlaybackModeEnabled {
|
timeline
|
||||||
playbackModeButton
|
.padding(.bottom, 2)
|
||||||
}
|
}
|
||||||
|
.zIndex(1)
|
||||||
|
.padding(.top, 2)
|
||||||
|
.transition(.opacity)
|
||||||
|
|
||||||
|
HStack(spacing: playerControlsLayout.buttonsSpacing) {
|
||||||
|
#if os(tvOS)
|
||||||
|
togglePlayButton
|
||||||
|
seekBackwardButton
|
||||||
|
seekForwardButton
|
||||||
|
#endif
|
||||||
|
if playerControlsRestartEnabled {
|
||||||
|
restartVideoButton
|
||||||
|
}
|
||||||
|
if playerControlsAdvanceToNextEnabled {
|
||||||
|
advanceToNextItemButton
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
#if os(tvOS)
|
||||||
|
if playerControlsSettingsEnabled {
|
||||||
|
settingsButton
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if playerControlsPlaybackModeEnabled {
|
||||||
|
playbackModeButton
|
||||||
|
}
|
||||||
|
#if os(tvOS)
|
||||||
|
closeVideoButton
|
||||||
|
#else
|
||||||
|
if playerControlsMusicModeEnabled {
|
||||||
|
musicModeButton
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
.zIndex(0)
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
closeVideoButton
|
.offset(y: -playerControlsLayout.timelineHeight - 30)
|
||||||
#else
|
#else
|
||||||
if playerControlsMusicModeEnabled {
|
.offset(y: -playerControlsLayout.timelineHeight - 5)
|
||||||
musicModeButton
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.zIndex(0)
|
|
||||||
#if os(tvOS)
|
|
||||||
.offset(y: -playerControlsLayout.timelineHeight - 30)
|
|
||||||
#else
|
|
||||||
.offset(y: -playerControlsLayout.timelineHeight - 5)
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
.opacity(model.presentingControls && !player.availableStreams.isEmpty ? 1 : 0)
|
||||||
}
|
}
|
||||||
.opacity(model.presentingControls && !player.availableStreams.isEmpty ? 1 : 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
@ -48,12 +48,12 @@ struct PlayerBackendView: View {
|
|||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
if player.activeBackend == .mpv || !avPlayerUsesSystemControls {
|
if player.activeBackend == .mpv || !avPlayerUsesSystemControls {
|
||||||
PlayerGestures()
|
PlayerGestures()
|
||||||
PlayerControls()
|
|
||||||
#if os(iOS)
|
|
||||||
.padding(.top, controlsTopPadding)
|
|
||||||
.padding(.bottom, controlsBottomPadding)
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
PlayerControls()
|
||||||
|
#if os(iOS)
|
||||||
|
.padding(.top, controlsTopPadding)
|
||||||
|
.padding(.bottom, controlsBottomPadding)
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
hiddenControlsButton
|
hiddenControlsButton
|
||||||
#endif
|
#endif
|
||||||
|
@ -5,42 +5,50 @@ struct PlayerGestures: View {
|
|||||||
private var player = PlayerModel.shared
|
private var player = PlayerModel.shared
|
||||||
@ObservedObject private var model = PlayerControlsModel.shared
|
@ObservedObject private var model = PlayerControlsModel.shared
|
||||||
|
|
||||||
|
@Default(.avPlayerUsesSystemControls) private var avPlayerUsesSystemControls
|
||||||
|
|
||||||
|
var showGestures: Bool {
|
||||||
|
player.activeBackend == .mpv || !avPlayerUsesSystemControls
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
gestureRectangle
|
if showGestures {
|
||||||
.tapRecognizer(
|
gestureRectangle
|
||||||
tapSensitivity: 0.2,
|
.tapRecognizer(
|
||||||
doubleTapAction: {
|
tapSensitivity: 0.2,
|
||||||
model.presentingControls = false
|
doubleTapAction: {
|
||||||
let interval = TimeInterval(Defaults[.gestureBackwardSeekDuration]) ?? 10
|
model.presentingControls = false
|
||||||
player.backend.seek(relative: .secondsInDefaultTimescale(-interval), seekType: .userInteracted)
|
let interval = TimeInterval(Defaults[.gestureBackwardSeekDuration]) ?? 10
|
||||||
},
|
player.backend.seek(relative: .secondsInDefaultTimescale(-interval), seekType: .userInteracted)
|
||||||
anyTapAction: {
|
},
|
||||||
singleTapAction()
|
anyTapAction: {
|
||||||
model.update()
|
singleTapAction()
|
||||||
}
|
model.update()
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
|
||||||
gestureRectangle
|
gestureRectangle
|
||||||
.tapRecognizer(
|
.tapRecognizer(
|
||||||
tapSensitivity: 0.2,
|
tapSensitivity: 0.2,
|
||||||
doubleTapAction: {
|
doubleTapAction: {
|
||||||
model.presentingControls = false
|
model.presentingControls = false
|
||||||
player.backend.togglePlay()
|
player.backend.togglePlay()
|
||||||
},
|
},
|
||||||
anyTapAction: singleTapAction
|
anyTapAction: singleTapAction
|
||||||
)
|
)
|
||||||
|
|
||||||
gestureRectangle
|
gestureRectangle
|
||||||
.tapRecognizer(
|
.tapRecognizer(
|
||||||
tapSensitivity: 0.2,
|
tapSensitivity: 0.2,
|
||||||
doubleTapAction: {
|
doubleTapAction: {
|
||||||
model.presentingControls = false
|
model.presentingControls = false
|
||||||
let interval = TimeInterval(Defaults[.gestureForwardSeekDuration]) ?? 10
|
let interval = TimeInterval(Defaults[.gestureForwardSeekDuration]) ?? 10
|
||||||
player.backend.seek(relative: .secondsInDefaultTimescale(interval), seekType: .userInteracted)
|
player.backend.seek(relative: .secondsInDefaultTimescale(interval), seekType: .userInteracted)
|
||||||
},
|
},
|
||||||
anyTapAction: singleTapAction
|
anyTapAction: singleTapAction
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2268,9 +2268,9 @@
|
|||||||
37992DC826CC50CD003D4C27 /* iOS */,
|
37992DC826CC50CD003D4C27 /* iOS */,
|
||||||
37BE0BD826A214500092E2DB /* macOS */,
|
37BE0BD826A214500092E2DB /* macOS */,
|
||||||
37D4B159267164AE00C925CA /* tvOS */,
|
37D4B159267164AE00C925CA /* tvOS */,
|
||||||
|
37D4B1B72672CFE300C925CA /* Model */,
|
||||||
37D4B0C12671614700C925CA /* Shared */,
|
37D4B0C12671614700C925CA /* Shared */,
|
||||||
3722AEBA274DA312005EA4D6 /* Backports */,
|
3722AEBA274DA312005EA4D6 /* Backports */,
|
||||||
37D4B1B72672CFE300C925CA /* Model */,
|
|
||||||
37C7A9022679058300E721B4 /* Extensions */,
|
37C7A9022679058300E721B4 /* Extensions */,
|
||||||
37DD9DCC2785EE6F00539416 /* Vendor */,
|
37DD9DCC2785EE6F00539416 /* Vendor */,
|
||||||
3748186426A762300084E870 /* Fixtures */,
|
3748186426A762300084E870 /* Fixtures */,
|
||||||
|
Loading…
Reference in New Issue
Block a user