-
Notifications
You must be signed in to change notification settings - Fork 0
/
VideoViewController.swift
148 lines (124 loc) · 5.24 KB
/
VideoViewController.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//
// VideoViewController.swift
// tinykittenstv
//
// Created by Christopher Trott on 4/13/17.
// Copyright © 2017 twocentstudios. All rights reserved.
//
import UIKit
import ReactiveSwift
import ReactiveCocoa
import Result
import XCDYouTubeKit
import AVFoundation
import Mortar
final class VideoViewController: UIViewController {
enum ViewState {
case inactive
case mayBecomeActive
case active
}
let client: XCDYouTubeClient
let videoInfo: LiveVideoInfo
lazy var playerView: PlayerView = PlayerView()
lazy var descriptionView: VideoDescriptionView = VideoDescriptionView()
lazy var loadingView: UIActivityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
let userPlayState = MutableProperty(UserPlayState.play)
let viewState = MutableProperty(ViewState.inactive)
private let loading: Property<Bool>
private let player: Property<AVPlayer?>
private let fetchAction: Action<(), XCDYouTubeVideo, NSError>
init(videoInfo: LiveVideoInfo, client: XCDYouTubeClient) {
self.videoInfo = videoInfo
self.client = client
fetchAction = Action { _ -> SignalProducer<XCDYouTubeVideo, NSError> in
return client.rac_getVideoWithIdentifier(videoInfo.id)
.start(on: QueueScheduler())
}
// TODO: error?
// let errors = fetchAction.errors.map { $0 }
let values = fetchAction.values
.map { (video: XCDYouTubeVideo) -> URL? in
return video.streamURLs[XCDYouTubeVideoQualityHTTPLiveStreaming]
}
.skipRepeats({ $0 == $1 })
.map { (url: URL?) -> AVPlayer? in
guard let url = url else { return nil }
let player = AVPlayer(url: url)
return player
}
self.player = ReactiveSwift.Property(initial: nil, then: values)
let infoLoading = fetchAction.isExecuting.producer.prefix(value: false)
let playerUnavailable = player.producer.map({ $0 == nil }).prefix(value: false)
let playerLoading = player.producer
.map { $0?.currentItem }
.skipNil()
.flatMap(.latest) { (playerItem: AVPlayerItem) -> Signal<Bool, NoError> in
return playerItem.reactive.signal(forKeyPath: #keyPath(AVPlayerItem.isPlaybackBufferEmpty)).skipNil().map({ $0 as! Bool })
}
.prefix(value: false)
let combinedLoading = SignalProducer.combineLatest(infoLoading, playerUnavailable, playerLoading).map({ $0 || $1 || $2 })
self.loading = Property<Bool>(initial: false, then: combinedLoading)
super.init(nibName: nil, bundle: nil)
self.player.producer
.observe(on: UIScheduler())
.startWithValues { [weak playerView] (player: AVPlayer?) in
playerView?.player = player
}
// TODO: may have to account for player error state to detect stale URL
SignalProducer.combineLatest(player.producer, userPlayState.producer, viewState.producer)
.startWithValues { [weak self] (player: AVPlayer?, userPlayState: UserPlayState, viewState: ViewState) in
if let player = player {
switch (userPlayState, viewState) {
case (_, .inactive):
player.pause()
case (_, .mayBecomeActive):
player.pause()
case (.play, .active):
player.play()
case (.pause, .active):
player.pause()
}
} else {
switch viewState {
case .mayBecomeActive, .active:
self?.fetchAction.apply(()).start()
case .inactive:
break
}
}
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
loadingView.color = Color.gray60
view |+| [
playerView,
loadingView,
descriptionView
]
[playerView, descriptionView] |=| view
loadingView.m_center |=| view
descriptionView.viewData = VideoDescriptionViewData(title: videoInfo.title, description: videoInfo.description)
descriptionView.alpha = 0
userPlayState.producer
.startWithValues { [unowned descriptionView] (playState: UserPlayState) in
let toAlpha: CGFloat = {
switch playState {
case .play: return 0
case .pause: return 1
}
}()
UIView.animate(withDuration: 0.2, animations: {
descriptionView.alpha = toAlpha
})
}
loading.producer
.startWithValues { [weak loadingView] (isLoading: Bool) in
isLoading ? loadingView?.startAnimating() : loadingView?.stopAnimating()
}
}
}