Playback state improvements

This commit is contained in:
Arkadiusz Fal 2021-08-23 23:31:51 +02:00
parent f80b61f9c7
commit 151121aa31
11 changed files with 55 additions and 27 deletions

View File

@ -1,2 +1 @@
5 5

View File

@ -3,6 +3,7 @@ parent_config: https://raw.githubusercontent.com/sindresorhus/swiftlint-config/m
disabled_rules: disabled_rules:
- identifier_name - identifier_name
- opening_brace - opening_brace
- number_separator
- multiline_arguments - multiline_arguments
excluded: excluded:

View File

@ -22,4 +22,9 @@ final class PlaybackState: ObservableObject {
return size.width / size.height return size.width / size.height
} }
func reset() {
stream = nil
time = nil
}
} }

View File

@ -19,14 +19,14 @@ final class PlayerState: ObservableObject {
private(set) var currentRate: Float = 0.0 private(set) var currentRate: Float = 0.0
static let availableRates: [Double] = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] static let availableRates: [Double] = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]
var playbackState: PlaybackState? var playbackState: PlaybackState
var timeObserver: Any? var timeObserver: Any?
let maxResolution: Stream.Resolution? let maxResolution: Stream.Resolution?
var playingOutsideViewController = false var playingOutsideViewController = false
init(_ video: Video? = nil, playbackState: PlaybackState? = nil, maxResolution: Stream.Resolution? = nil) { init(_ video: Video? = nil, playbackState: PlaybackState, maxResolution: Stream.Resolution? = nil) {
self.video = video self.video = video
self.playbackState = playbackState self.playbackState = playbackState
self.maxResolution = maxResolution self.maxResolution = maxResolution
@ -41,6 +41,8 @@ final class PlayerState: ObservableObject {
return return
} }
playbackState.reset()
loadExtendedVideoDetails(video) { video in loadExtendedVideoDetails(video) { video in
self.video = video self.video = video
self.playVideo(video) self.playVideo(video)
@ -98,12 +100,16 @@ final class PlayerState: ObservableObject {
} }
fileprivate func playStream(_ stream: Stream) { fileprivate func playStream(_ stream: Stream) {
guard player != nil else {
return
}
logger.warning("loading \(stream.description) to player") logger.warning("loading \(stream.description) to player")
DispatchQueue.main.async { DispatchQueue.main.async {
self.saveTime() self.saveTime()
self.player?.replaceCurrentItem(with: self.playerItemWithMetadata(for: stream)) self.player?.replaceCurrentItem(with: self.playerItemWithMetadata(for: stream))
self.playbackState?.stream = stream self.playbackState.stream = stream
if self.timeObserver == nil { if self.timeObserver == nil {
self.addTimeObserver() self.addTimeObserver()
} }
@ -259,7 +265,7 @@ final class PlayerState: ObservableObject {
self.player.rate = self.currentRate self.player.rate = self.currentRate
} }
self.playbackState?.time = self.player.currentTime() self.playbackState.time = self.player.currentTime()
} }
} }

View File

@ -2,6 +2,7 @@ import SwiftUI
struct ContentView: View { struct ContentView: View {
@StateObject private var navigationState = NavigationState() @StateObject private var navigationState = NavigationState()
@StateObject private var playbackState = PlaybackState()
@StateObject private var searchState = SearchState() @StateObject private var searchState = SearchState()
#if os(iOS) #if os(iOS)
@ -37,6 +38,7 @@ struct ContentView: View {
} }
#endif #endif
.environmentObject(navigationState) .environmentObject(navigationState)
.environmentObject(playbackState)
.environmentObject(searchState) .environmentObject(searchState)
} }
} }

View File

@ -2,25 +2,32 @@ import Foundation
import SwiftUI import SwiftUI
struct PlaybackBar: View { struct PlaybackBar: View {
@Environment(\.dismiss) private var dismiss
@ObservedObject var playbackState: PlaybackState
let video: Video let video: Video
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var playbackState: PlaybackState
var body: some View { var body: some View {
HStack { HStack {
closeButton closeButton
.frame(minWidth: 0, maxWidth: 60, alignment: .leading) .frame(width: 60, alignment: .leading)
Text(playbackFinishAtString) Text(playbackFinishAtString)
.foregroundColor(.gray) .foregroundColor(.gray)
.font(.caption2) .font(.caption2)
.frame(minWidth: 0, maxWidth: .infinity) .frame(minWidth: 60, maxWidth: .infinity)
Text(currentStreamString) VStack {
.foregroundColor(.gray) if playbackState.stream != nil {
.font(.caption2) Text(currentStreamString)
.frame(minWidth: 0, maxWidth: 60, alignment: .trailing) } else {
Image(systemName: "bolt.horizontal.fill")
}
}
.foregroundColor(.gray)
.font(.caption2)
.frame(width: 60, alignment: .trailing)
.fixedSize(horizontal: true, vertical: true)
} }
.padding(4) .padding(4)
.background(.black) .background(.black)
@ -37,6 +44,10 @@ struct PlaybackBar: View {
let remainingSeconds = video.length - playbackState.time!.seconds let remainingSeconds = video.length - playbackState.time!.seconds
if remainingSeconds < 60 {
return "less than a minute"
}
let timeFinishAt = Date.now.addingTimeInterval(remainingSeconds) let timeFinishAt = Date.now.addingTimeInterval(remainingSeconds)
let timeFinishAtString = timeFinishAt.formatted(date: .omitted, time: .shortened) let timeFinishAtString = timeFinishAt.formatted(date: .omitted, time: .shortened)

View File

@ -1,14 +1,15 @@
import SwiftUI import SwiftUI
struct Player: UIViewControllerRepresentable { struct Player: UIViewControllerRepresentable {
@ObservedObject var playbackState: PlaybackState @EnvironmentObject<PlaybackState> private var playbackState
var video: Video? var video: Video?
func makeUIViewController(context _: Context) -> PlayerViewController { func makeUIViewController(context _: Context) -> PlayerViewController {
let controller = PlayerViewController() let controller = PlayerViewController()
controller.playbackState = playbackState
controller.video = video controller.video = video
controller.playbackState = playbackState
return controller return controller
} }

View File

@ -34,8 +34,7 @@ final class PlayerViewController: UIViewController {
} }
func loadPlayer() { func loadPlayer() {
playerState = PlayerState() playerState = PlayerState(playbackState: playbackState)
playerState.playbackState = playbackState
guard !playerLoaded else { guard !playerLoaded else {
return return

View File

@ -13,11 +13,10 @@ struct VideoPlayerView: View {
} }
@EnvironmentObject<NavigationState> private var navigationState @EnvironmentObject<NavigationState> private var navigationState
@EnvironmentObject<PlaybackState> private var playbackState
@ObservedObject private var store = Store<Video>() @ObservedObject private var store = Store<Video>()
@ObservedObject private var playbackState = PlaybackState()
#if os(iOS) #if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass @Environment(\.verticalSizeClass) private var verticalSizeClass
#endif #endif
@ -36,19 +35,21 @@ struct VideoPlayerView: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
#if os(tvOS) #if os(tvOS)
Player(playbackState: playbackState, video: video) Player(video: video)
.environmentObject(playbackState)
#else #else
GeometryReader { geometry in GeometryReader { geometry in
VStack(spacing: 0) { VStack(spacing: 0) {
#if os(iOS) #if os(iOS)
if verticalSizeClass == .regular { if verticalSizeClass == .regular {
PlaybackBar(playbackState: playbackState, video: video) PlaybackBar(video: video)
} }
#elseif os(macOS) #elseif os(macOS)
PlaybackBar(playbackState: playbackState, video: video) PlaybackBar(video: video)
#endif #endif
Player(playbackState: playbackState, video: video) Player(video: video)
.environmentObject(playbackState)
.modifier(VideoPlayerSizeModifier(geometry: geometry, aspectRatio: playbackState.aspectRatio)) .modifier(VideoPlayerSizeModifier(geometry: geometry, aspectRatio: playbackState.aspectRatio))
} }
.background(.black) .background(.black)

View File

@ -1,14 +1,15 @@
import SwiftUI import SwiftUI
struct Player: NSViewControllerRepresentable { struct Player: NSViewControllerRepresentable {
@ObservedObject var playbackState: PlaybackState @EnvironmentObject<PlaybackState> private var playbackState
var video: Video! var video: Video!
func makeNSViewController(context _: Context) -> PlayerViewController { func makeNSViewController(context _: Context) -> PlayerViewController {
let controller = PlayerViewController() let controller = PlayerViewController()
controller.playbackState = playbackState
controller.video = video controller.video = video
controller.playbackState = playbackState
return controller return controller
} }

View File

@ -3,6 +3,7 @@ import SwiftUI
struct TVNavigationView: View { struct TVNavigationView: View {
@EnvironmentObject<NavigationState> private var navigationState @EnvironmentObject<NavigationState> private var navigationState
@EnvironmentObject<PlaybackState> private var playbackState
@State private var showingOptions = false @State private var showingOptions = false
@ -47,6 +48,7 @@ struct TVNavigationView: View {
.fullScreenCover(isPresented: $navigationState.showingVideo) { .fullScreenCover(isPresented: $navigationState.showingVideo) {
if let video = navigationState.video { if let video = navigationState.video {
VideoPlayerView(video) VideoPlayerView(video)
.environmentObject(playbackState)
} }
} }
.onPlayPauseCommand { showingOptions.toggle() } .onPlayPauseCommand { showingOptions.toggle() }