Show stream opening status with AVPlayer

This commit is contained in:
Arkadiusz Fal 2023-05-21 12:09:18 +02:00
parent 2b7ccc4b03
commit c6798be167
6 changed files with 174 additions and 143 deletions

View File

@ -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,

View File

@ -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)")

View File

@ -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)

View File

@ -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

View File

@ -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
) )
}
} }
} }

View File

@ -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 */,