Add initial version of music mode

This commit is contained in:
Arkadiusz Fal 2022-06-07 23:27:48 +02:00
parent cf6077aaf3
commit 89713d815a
8 changed files with 142 additions and 41 deletions

View File

@ -62,6 +62,10 @@ final class MPVBackend: PlayerBackend {
private var controlsUpdates = false private var controlsUpdates = false
private var timeObserverThrottle = Throttle(interval: 2) private var timeObserverThrottle = Throttle(interval: 2)
var tracks: Int {
client?.tracksCount ?? -1
}
init(model: PlayerModel, controls: PlayerControlsModel? = nil) { init(model: PlayerModel, controls: PlayerControlsModel? = nil) {
self.model = model self.model = model
self.controls = controls self.controls = controls

View File

@ -50,7 +50,7 @@ final class PiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
} }
#endif #endif
if !player.currentItem.isNil { if !player.currentItem.isNil, !player.musicMode {
player?.show() player?.show()
} }

View File

@ -68,9 +68,15 @@ final class PlayerControlsModel: ObservableObject {
} }
func hide() { func hide() {
player?.backend.stopControlsUpdates() guard let player = player,
!player.musicMode
else {
return
}
guard !(player?.currentItem.isNil ?? true) else { player.backend.stopControlsUpdates()
guard !player.currentItem.isNil else {
return return
} }
@ -83,9 +89,7 @@ final class PlayerControlsModel: ObservableObject {
} }
func toggle() { func toggle() {
withAnimation(PlayerControls.animation) { presentingControls ? hide() : show()
presentingControls.toggle()
}
} }
func reset() { func reset() {
@ -101,6 +105,11 @@ final class PlayerControlsModel: ObservableObject {
#endif #endif
removeTimer() removeTimer()
guard !player.musicMode else {
return
}
timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { _ in timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { _ in
withAnimation(PlayerControls.animation) { [weak self] in withAnimation(PlayerControls.animation) { [weak self] in
self?.presentingControls = false self?.presentingControls = false

View File

@ -62,6 +62,7 @@ final class PlayerModel: ObservableObject {
@Published var lastSkipped: Segment? { didSet { rebuildTVMenu() } } @Published var lastSkipped: Segment? { didSet { rebuildTVMenu() } }
@Published var restoredSegments = [Segment]() @Published var restoredSegments = [Segment]()
@Published var musicMode = false
@Published var returnYouTubeDislike = ReturnYouTubeDislikeAPI() @Published var returnYouTubeDislike = ReturnYouTubeDislikeAPI()
#if os(iOS) #if os(iOS)
@ -114,7 +115,7 @@ final class PlayerModel: ObservableObject {
self.avPlayerBackend = AVPlayerBackend(model: self, controls: controls) self.avPlayerBackend = AVPlayerBackend(model: self, controls: controls)
self.mpvBackend = MPVBackend(model: self) self.mpvBackend = MPVBackend(model: self)
self.activeBackend = Defaults[.activeBackend] Defaults[.activeBackend] = .mpv
} }
func show() { func show() {
@ -361,6 +362,12 @@ final class PlayerModel: ObservableObject {
return return
} }
if to == .mpv {
addVideoTrackFromStream()
} else {
musicMode = false
}
inactiveBackends().forEach { $0.pause() } inactiveBackends().forEach { $0.pause() }
let fromBackend: PlayerBackend = from == .appleAVPlayer ? avPlayerBackend : mpvBackend let fromBackend: PlayerBackend = from == .appleAVPlayer ? avPlayerBackend : mpvBackend
@ -561,4 +568,37 @@ final class PlayerModel: ObservableObject {
func setNeedsDrawing(_ needsDrawing: Bool) { func setNeedsDrawing(_ needsDrawing: Bool) {
backends.forEach { $0.setNeedsDrawing(needsDrawing) } backends.forEach { $0.setNeedsDrawing(needsDrawing) }
} }
func toggleMusicMode() {
musicMode.toggle()
if musicMode {
if playingInPictureInPicture {
avPlayerBackend.pause()
avPlayerBackend.switchToMPVOnPipClose = false
closePiP()
}
changeActiveBackend(from: .appleAVPlayer, to: .mpv)
controls.presentingControls = true
controls.removeTimer()
mpvBackend.setVideoToNo()
} else {
addVideoTrackFromStream()
mpvBackend.setVideoToAuto()
controls.resetTimer()
}
}
func addVideoTrackFromStream() {
if let videoTrackURL = stream?.videoAsset?.url,
mpvBackend.tracks < 2
{
logger.info("adding video track")
mpvBackend.addVideoTrack(videoTrackURL)
}
mpvBackend.setVideoToAuto()
}
} }

View File

@ -1,10 +1,12 @@
import Foundation import Foundation
import SDWebImageSwiftUI
import SwiftUI import SwiftUI
struct PlayerControls: View { struct PlayerControls: View {
static let animation = Animation.easeInOut(duration: 0.2) static let animation = Animation.easeInOut(duration: 0.2)
private var player: PlayerModel! private var player: PlayerModel!
private var thumbnails: ThumbnailsModel!
@EnvironmentObject<PlayerControlsModel> private var model @EnvironmentObject<PlayerControlsModel> private var model
@ -20,8 +22,9 @@ struct PlayerControls: View {
@FocusState private var focusedField: Field? @FocusState private var focusedField: Field?
#endif #endif
init(player: PlayerModel) { init(player: PlayerModel, thumbnails: ThumbnailsModel) {
self.player = player self.player = player
self.thumbnails = thumbnails
} }
var body: some View { var body: some View {
@ -86,10 +89,26 @@ struct PlayerControls: View {
} }
#else #else
.background(PlayerGestures()) .background(PlayerGestures())
.background(controlsBackground)
#endif #endif
.environment(\.colorScheme, .dark) .environment(\.colorScheme, .dark)
} }
@ViewBuilder var controlsBackground: some View {
if player.musicMode,
let item = self.player.currentItem,
let url = thumbnails.best(item.video)
{
WebImage(url: url)
.resizable()
.placeholder {
Rectangle().fill(Color("PlaceholderColor"))
}
.retryOnAppear(true)
.indicator(.activity)
}
}
var timeline: some View { var timeline: some View {
TimelineView(duration: durationBinding, current: currentTimeBinding, cornerRadius: 0) TimelineView(duration: durationBinding, current: currentTimeBinding, cornerRadius: 0)
} }
@ -186,9 +205,11 @@ struct PlayerControls: View {
closeVideoButton closeVideoButton
button("Music Mode", systemImage: "music.note", active: player.musicMode, action: player.toggleMusicMode)
.disabled(player.activeBackend == .appleAVPlayer)
Spacer() Spacer()
#endif #endif
// button("Music Mode", systemImage: "music.note")
} }
} }
@ -355,6 +376,7 @@ struct PlayerControls: View {
systemImage: String = "arrow.up.left.and.arrow.down.right", systemImage: String = "arrow.up.left.and.arrow.down.right",
size: Double = 30, size: Double = 30,
cornerRadius: Double = 3, cornerRadius: Double = 3,
active: Bool = false,
action: @escaping () -> Void = {} action: @escaping () -> Void = {}
) -> some View { ) -> some View {
Button { Button {
@ -367,7 +389,7 @@ struct PlayerControls: View {
.contentShape(Rectangle()) .contentShape(Rectangle())
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.foregroundColor(.primary) .foregroundColor(active ? .accentColor : .primary)
.frame(width: size, height: size) .frame(width: size, height: size)
#if os(macOS) #if os(macOS)
.background(VisualEffectBlur(material: .hudWindow)) .background(VisualEffectBlur(material: .hudWindow))
@ -396,7 +418,7 @@ struct PlayerControls_Previews: PreviewProvider {
let view = ZStack { let view = ZStack {
Color.gray Color.gray
PlayerControls(player: PlayerModel()) PlayerControls(player: PlayerModel(), thumbnails: ThumbnailsModel())
.injectFixtureEnvironmentObjects() .injectFixtureEnvironmentObjects()
.environmentObject(model) .environmentObject(model)
} }

View File

@ -48,6 +48,7 @@ struct VideoPlayerView: View {
@EnvironmentObject<AccountsModel> private var accounts @EnvironmentObject<AccountsModel> private var accounts
@EnvironmentObject<PlayerControlsModel> private var playerControls @EnvironmentObject<PlayerControlsModel> private var playerControls
@EnvironmentObject<PlayerModel> private var player @EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<ThumbnailsModel> private var thumbnails
var body: some View { var body: some View {
#if os(macOS) #if os(macOS)
@ -126,9 +127,7 @@ struct VideoPlayerView: View {
#else #else
GeometryReader { geometry in GeometryReader { geometry in
VStack(spacing: 0) { VStack(spacing: 0) {
if player.currentItem.isNil { if player.playingInPictureInPicture {
playerPlaceholder(geometry: geometry)
} else if player.playingInPictureInPicture {
pictureInPicturePlaceholder(geometry: geometry) pictureInPicturePlaceholder(geometry: geometry)
} else { } else {
playerView playerView
@ -140,6 +139,7 @@ struct VideoPlayerView: View {
fullScreen: playerControls.playingFullscreen fullScreen: playerControls.playingFullscreen
) )
) )
.overlay(playerPlaceholder(geometry: geometry))
#endif #endif
} }
} }
@ -273,7 +273,7 @@ struct VideoPlayerView: View {
PlayerGestures() PlayerGestures()
#endif #endif
PlayerControls(player: player) PlayerControls(player: player, thumbnails: thumbnails)
} }
#if os(iOS) #if os(iOS)
.onAppear { .onAppear {
@ -298,7 +298,8 @@ struct VideoPlayerView: View {
#endif #endif
} }
func playerPlaceholder(geometry: GeometryProxy) -> some View { @ViewBuilder func playerPlaceholder(geometry: GeometryProxy) -> some View {
if player.currentItem.isNil {
ZStack(alignment: .topLeading) { ZStack(alignment: .topLeading) {
HStack { HStack {
Spacer() Spacer()
@ -328,9 +329,11 @@ struct VideoPlayerView: View {
.foregroundColor(.gray) .foregroundColor(.gray)
#endif #endif
} }
.background(Color.black)
.contentShape(Rectangle()) .contentShape(Rectangle())
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: geometry.size.width / Self.defaultAspectRatio) .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: geometry.size.width / Self.defaultAspectRatio)
} }
}
func pictureInPicturePlaceholder(geometry: GeometryProxy) -> some View { func pictureInPicturePlaceholder(geometry: GeometryProxy) -> some View {
HStack { HStack {

View File

@ -65,6 +65,10 @@ struct VideoCell: View {
return return
} }
if player.musicMode {
player.toggleMusicMode()
}
if watchingNow { if watchingNow {
if !player.playingInPictureInPicture { if !player.playingInPictureInPicture {
player.show() player.show()

View File

@ -57,6 +57,9 @@ struct VideoContextMenuView: View {
#if os(iOS) #if os(iOS)
playNowInPictureInPictureButton playNowInPictureInPictureButton
#endif #endif
#if !os(tvOS)
playNowInMusicMode
#endif
} }
Section { Section {
@ -131,6 +134,10 @@ struct VideoContextMenuView: View {
private var playNowButton: some View { private var playNowButton: some View {
Button { Button {
if player.musicMode {
player.toggleMusicMode()
}
player.play(video) player.play(video)
} label: { } label: {
Label("Play Now", systemImage: "play") Label("Play Now", systemImage: "play")
@ -149,6 +156,18 @@ struct VideoContextMenuView: View {
} }
} }
private var playNowInMusicMode: some View {
Button {
if !player.musicMode {
player.toggleMusicMode()
}
player.play(video, at: watch?.timeToRestart, showingPlayer: false)
} label: {
Label("Play Music", systemImage: "music.note")
}
}
private var playNextButton: some View { private var playNextButton: some View {
Button { Button {
player.playNext(video) player.playNext(video)