Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
//  Player.swift
2
//
3
//  Created by patrick piemonte on 11/26/14.
4
//
5
//  The MIT License (MIT)
6
//
7
//  Copyright (c) 2014-present patrick piemonte (http://patrickpiemonte.com/)
8
//
9
//  Permission is hereby granted, free of charge, to any person obtaining a copy
10
//  of this software and associated documentation files (the "Software"), to deal
11
//  in the Software without restriction, including without limitation the rights
12
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
//  copies of the Software, and to permit persons to whom the Software is
14
//  furnished to do so, subject to the following conditions:
15
//
16
//  The above copyright notice and this permission notice shall be included in all
17
//  copies or substantial portions of the Software.
18
//
19
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
//  SOFTWARE.
26
 
27
import UIKit
28
import Foundation
29
import AVFoundation
30
import CoreGraphics
31
 
32
// MARK: - error types
33
 
34
/// Error domain for all Player errors.
35
public let PlayerErrorDomain = "PlayerErrorDomain"
36
 
37
/// Error types.
38
public enum PlayerError: Error, CustomStringConvertible {
39
    case failed
40
 
41
    public var description: String {
42
        get {
43
            switch self {
44
            case .failed:
45
                return "failed"
46
            }
47
        }
48
    }
49
}
50
 
51
// MARK: - PlayerDelegate
52
 
53
/// Player delegate protocol
54
public protocol PlayerDelegate: AnyObject {
55
    func playerReady(_ player: Player)
56
    func playerPlaybackStateDidChange(_ player: Player)
57
    func playerBufferingStateDidChange(_ player: Player)
58
 
59
    // This is the time in seconds that the video has been buffered.
60
    // If implementing a UIProgressView, user this value / player.maximumDuration to set progress.
61
    func playerBufferTimeDidChange(_ bufferTime: Double)
62
 
63
    func player(_ player: Player, didFailWithError error: Error?)
64
}
65
 
66
 
67
/// Player playback protocol
68
public protocol PlayerPlaybackDelegate: AnyObject {
69
    func playerCurrentTimeDidChange(_ player: Player)
70
    func playerPlaybackWillStartFromBeginning(_ player: Player)
71
    func playerPlaybackDidEnd(_ player: Player)
72
    func playerPlaybackWillLoop(_ player: Player)
73
    func playerPlaybackDidLoop(_ player: Player)
74
}
75
 
76
// MARK: - Player
77
 
78
/// ▶️ Player, simple way to play and stream media
79
open class Player: UIViewController {
80
 
81
    // types
82
 
83
    /// Video fill mode options for `Player.fillMode`.
84
    ///
85
    /// - resize: Stretch to fill.
86
    /// - resizeAspectFill: Preserve aspect ratio, filling bounds.
87
    /// - resizeAspectFit: Preserve aspect ratio, fill within bounds.
88
    public typealias FillMode = AVLayerVideoGravity
89
 
90
    /// Asset playback states.
91
    public enum PlaybackState: Int, CustomStringConvertible {
92
        case stopped = 0
93
        case playing
94
        case paused
95
        case failed
96
 
97
        public var description: String {
98
            get {
99
                switch self {
100
                case .stopped:
101
                    return "Stopped"
102
                case .playing:
103
                    return "Playing"
104
                case .failed:
105
                    return "Failed"
106
                case .paused:
107
                    return "Paused"
108
                }
109
            }
110
        }
111
    }
112
 
113
    /// Asset buffering states.
114
    public enum BufferingState: Int, CustomStringConvertible {
115
        case unknown = 0
116
        case ready
117
        case delayed
118
 
119
        public var description: String {
120
            get {
121
                switch self {
122
                case .unknown:
123
                    return "Unknown"
124
                case .ready:
125
                    return "Ready"
126
                case .delayed:
127
                    return "Delayed"
128
                }
129
            }
130
        }
131
    }
132
 
133
    // properties
134
 
135
    /// Player delegate.
136
    open weak var playerDelegate: PlayerDelegate?
137
 
138
    /// Playback delegate.
139
    open weak var playbackDelegate: PlayerPlaybackDelegate?
140
 
141
    // configuration
142
 
143
    /// Local or remote URL for the file asset to be played.
144
    ///
145
    /// - Parameter url: URL of the asset.
146
    open var url: URL? {
147
        didSet {
148
            if let url = self.url {
149
                setup(url: url)
150
            }
151
        }
152
    }
153
 
154
    /// For setting up with AVAsset instead of URL
155
    /// Note: This will reset the `url` property. (cannot set both)
156
    open var asset: AVAsset? {
157
        get { return _asset }
158
        set { _ = newValue.map { setupAsset($0) } }
159
    }
160
 
161
    /// Specifies how the video is displayed within a player layer’s bounds.
162
    /// The default value is `AVLayerVideoGravityResizeAspect`. See `PlayerFillMode`.
163
    open var fillMode: Player.FillMode {
164
        get {
165
            return self._playerView.playerFillMode
166
        }
167
        set {
168
            self._playerView.playerFillMode = newValue
169
        }
170
    }
171
 
172
    /// Determines if the video should autoplay when streaming a URL.
173
    open var autoplay: Bool = true
174
 
175
    /// Mutes audio playback when true.
176
    open var muted: Bool {
177
        get {
178
            return self._avplayer.isMuted
179
        }
180
        set {
181
            self._avplayer.isMuted = newValue
182
        }
183
    }
184
 
185
    /// Volume for the player, ranging from 0.0 to 1.0 on a linear scale.
186
    open var volume: Float {
187
        get {
188
            return self._avplayer.volume
189
        }
190
        set {
191
            self._avplayer.volume = newValue
192
        }
193
    }
194
 
195
    /// Pauses playback automatically when resigning active.
196
    open var playbackPausesWhenResigningActive: Bool = true
197
 
198
    /// Pauses playback automatically when backgrounded.
199
    open var playbackPausesWhenBackgrounded: Bool = true
200
 
201
    /// Resumes playback when became active.
202
    open var playbackResumesWhenBecameActive: Bool = true
203
 
204
    /// Resumes playback when entering foreground.
205
    open var playbackResumesWhenEnteringForeground: Bool = true
206
 
207
    // state
208
 
209
    open var isPlayingVideo: Bool {
210
        get {
211
            guard let asset = self._asset else {
212
                return false
213
            }
214
            return asset.tracks(withMediaType: .video).count != 0
215
        }
216
    }
217
 
218
    /// Playback automatically loops continuously when true.
219
    open var playbackLoops: Bool {
220
        get {
221
            return self._avplayer.actionAtItemEnd == .none
222
        }
223
        set {
224
            if newValue {
225
                self._avplayer.actionAtItemEnd = .none
226
            } else {
227
                self._avplayer.actionAtItemEnd = .pause
228
            }
229
        }
230
    }
231
 
232
    /// Playback freezes on last frame frame when true and does not reset seek position timestamp..
233
    open var playbackFreezesAtEnd: Bool = false
234
 
235
    /// Current playback state of the Player.
236
    open var playbackState: PlaybackState = .stopped {
237
        didSet {
238
            if playbackState != oldValue || !playbackEdgeTriggered {
239
                self.executeClosureOnMainQueueIfNecessary {
240
                    self.playerDelegate?.playerPlaybackStateDidChange(self)
241
                }
242
            }
243
        }
244
    }
245
 
246
    /// Current buffering state of the Player.
247
    open var bufferingState: BufferingState = .unknown {
248
        didSet {
249
            if bufferingState != oldValue || !playbackEdgeTriggered {
250
                self.executeClosureOnMainQueueIfNecessary {
251
                    self.playerDelegate?.playerBufferingStateDidChange(self)
252
                }
253
            }
254
        }
255
    }
256
 
257
    /// Playback buffering size in seconds.
258
    open var bufferSizeInSeconds: Double = 10
259
 
260
    /// Playback is not automatically triggered from state changes when true.
261
    open var playbackEdgeTriggered: Bool = true
262
 
263
    /// Maximum duration of playback.
264
    open var maximumDuration: TimeInterval {
265
        get {
266
            if let playerItem = self._playerItem {
267
                return CMTimeGetSeconds(playerItem.duration)
268
            } else {
269
                return CMTimeGetSeconds(CMTime.indefinite)
270
            }
271
        }
272
    }
273
 
274
    /// Media playback's current time interval in seconds.
275
    open var currentTimeInterval: TimeInterval {
276
        get {
277
            if let playerItem = self._playerItem {
278
                return CMTimeGetSeconds(playerItem.currentTime())
279
            } else {
280
                return CMTimeGetSeconds(CMTime.indefinite)
281
            }
282
        }
283
    }
284
 
285
    /// Media playback's current time.
286
    open var currentTime: CMTime {
287
        get {
288
            if let playerItem = self._playerItem {
289
                return playerItem.currentTime()
290
            } else {
291
                return CMTime.indefinite
292
            }
293
        }
294
    }
295
 
296
    /// The natural dimensions of the media.
297
    open var naturalSize: CGSize {
298
        get {
299
            if let playerItem = self._playerItem,
300
                let track = playerItem.asset.tracks(withMediaType: .video).first {
301
 
302
                let size = track.naturalSize.applying(track.preferredTransform)
303
                return CGSize(width: abs(size.width), height: abs(size.height))
304
            } else {
305
                return CGSize.zero
306
            }
307
        }
308
    }
309
 
310
    /// self.view as PlayerView type
311
    public var playerView: PlayerView {
312
        get {
313
            return self._playerView
314
        }
315
    }
316
 
317
    /// Return the av player layer for consumption by things such as Picture in Picture
318
    open func playerLayer() -> AVPlayerLayer? {
319
        return self._playerView.playerLayer
320
    }
321
 
322
    /// Indicates the desired limit of network bandwidth consumption for this item.
323
    open var preferredPeakBitRate: Double = 0 {
324
        didSet {
325
            self._playerItem?.preferredPeakBitRate = self.preferredPeakBitRate
326
        }
327
    }
328
 
329
    /// Indicates a preferred upper limit on the resolution of the video to be downloaded.
330
    @available(iOS 11.0, tvOS 11.0, *)
331
    open var preferredMaximumResolution: CGSize {
332
        get {
333
            return self._playerItem?.preferredMaximumResolution ?? CGSize.zero
334
        }
335
        set {
336
            self._playerItem?.preferredMaximumResolution = newValue
337
            self._preferredMaximumResolution = newValue
338
        }
339
    }
340
 
341
    // MARK: - private instance vars
342
 
343
    internal var _asset: AVAsset? {
344
        didSet {
345
            if let _ = self._asset {
346
                self.setupPlayerItem(nil)
347
            }
348
        }
349
    }
350
    internal lazy var _avplayer: AVPlayer = {
351
        let avplayer = AVPlayer()
352
        avplayer.actionAtItemEnd = .pause
353
        return avplayer
354
    }()
355
    internal var _playerItem: AVPlayerItem?
356
 
357
    internal var _playerObservers = [NSKeyValueObservation]()
358
    internal var _playerItemObservers = [NSKeyValueObservation]()
359
    internal var _playerLayerObserver: NSKeyValueObservation?
360
    internal var _playerTimeObserver: Any?
361
 
362
    internal var _playerView: PlayerView = PlayerView(frame: .zero)
363
    internal var _seekTimeRequested: CMTime?
364
    internal var _lastBufferTime: Double = 0
365
    internal var _preferredMaximumResolution: CGSize = .zero
366
 
367
    // Boolean that determines if the user or calling coded has trigged autoplay manually.
368
    internal var _hasAutoplayActivated: Bool = true
369
 
370
    // MARK: - object lifecycle
371
 
372
    public convenience init() {
373
        self.init(nibName: nil, bundle: nil)
374
    }
375
 
376
    public required init?(coder aDecoder: NSCoder) {
377
        super.init(coder: aDecoder)
378
    }
379
 
380
    public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
381
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
382
    }
383
 
384
    deinit {
385
        self._avplayer.pause()
386
        self.setupPlayerItem(nil)
387
 
388
        self.removePlayerObservers()
389
 
390
        self.playerDelegate = nil
391
        self.removeApplicationObservers()
392
 
393
        self.playbackDelegate = nil
394
        self.removePlayerLayerObservers()
395
 
396
        self._playerView.player = nil
397
    }
398
 
399
    // MARK: - view lifecycle
400
 
401
    open override func loadView() {
402
        super.loadView()
403
        self._playerView.frame = self.view.bounds
404
        self.view = self._playerView
405
    }
406
 
407
    open override func viewDidLoad() {
408
        super.viewDidLoad()
409
        self._playerView.player = self._avplayer
410
 
411
        if let url = self.url {
412
            setup(url: url)
413
        } else if let asset = self.asset {
414
            setupAsset(asset)
415
        }
416
 
417
        self.addPlayerLayerObservers()
418
        self.addPlayerObservers()
419
        self.addApplicationObservers()
420
    }
421
 
422
    open override func viewDidDisappear(_ animated: Bool) {
423
        super.viewDidDisappear(animated)
424
        if self.playbackState == .playing {
425
            self.pause()
426
        }
427
    }
428
 
429
}
430
 
431
// MARK: - performance
432
 
433
extension Player {
434
 
435
    /// Total time spent playing.
436
    public var totalDurationWatched: TimeInterval {
437
        get {
438
            var totalDurationWatched = 0.0
439
            if let accessLog = self._playerItem?.accessLog(), accessLog.events.isEmpty == false {
440
                for event in accessLog.events where event.durationWatched > 0 {
441
                    totalDurationWatched += event.durationWatched
442
                }
443
            }
444
            return totalDurationWatched
445
        }
446
    }
447
 
448
    /// Time weighted value of the variant indicated bitrate. Measure of overall stream quality.
449
    var timeWeightedIBR: Double {
450
        var timeWeightedIBR = 0.0
451
        let totalDurationWatched = self.totalDurationWatched
452
 
453
        if let accessLog = self._playerItem?.accessLog(), totalDurationWatched > 0 {
454
            for event in accessLog.events {
455
                if event.durationWatched > 0 && event.indicatedBitrate > 0 {
456
                    let eventTimeWeight = event.durationWatched / totalDurationWatched
457
                    timeWeightedIBR += event.indicatedBitrate * eventTimeWeight
458
                }
459
            }
460
        }
461
        return timeWeightedIBR
462
    }
463
 
464
    /// Stall rate measured in stalls per hour. Normalized measure of stream interruptions caused by stream buffer depleation.
465
    var stallRate: Double {
466
        var totalNumberOfStalls = 0
467
        let totalHoursWatched = self.totalDurationWatched / 3600
468
 
469
        if let accessLog = self._playerItem?.accessLog(), totalDurationWatched > 0 {
470
            for event in accessLog.events {
471
                totalNumberOfStalls += event.numberOfStalls
472
            }
473
        }
474
        return Double(totalNumberOfStalls) / totalHoursWatched
475
    }
476
 
477
}
478
 
479
// MARK: - actions
480
 
481
extension Player {
482
 
483
    /// Begins playback of the media from the beginning.
484
    open func playFromBeginning() {
485
        self.playbackDelegate?.playerPlaybackWillStartFromBeginning(self)
486
        self._avplayer.seek(to: CMTime.zero)
487
        self.playFromCurrentTime()
488
    }
489
 
490
    /// Begins playback of the media from the current time.
491
    open func playFromCurrentTime() {
492
        if !self.autoplay {
493
            // External call to this method with autoplay disabled. Re-activate it before calling play.
494
            self._hasAutoplayActivated = true
495
        }
496
        self.play()
497
    }
498
 
499
    fileprivate func play() {
500
        if self.autoplay || self._hasAutoplayActivated {
501
            self.playbackState = .playing
502
            self._avplayer.play()
503
        }
504
    }
505
 
506
    /// Pauses playback of the media.
507
    open func pause() {
508
        if self.playbackState != .playing {
509
            return
510
        }
511
 
512
        self._avplayer.pause()
513
        self.playbackState = .paused
514
    }
515
 
516
    /// Stops playback of the media.
517
    open func stop() {
518
        if self.playbackState == .stopped {
519
            return
520
        }
521
 
522
        self._avplayer.pause()
523
        self.playbackState = .stopped
524
        self.playbackDelegate?.playerPlaybackDidEnd(self)
525
    }
526
 
527
    /// Updates playback to the specified time.
528
    ///
529
    /// - Parameters:
530
    ///   - time: The time to switch to move the playback.
531
    ///   - completionHandler: Call block handler after seeking/
532
    open func seek(to time: CMTime, completionHandler: ((Bool) -> Swift.Void)? = nil) {
533
        if let playerItem = self._playerItem {
534
            return playerItem.seek(to: time, completionHandler: completionHandler)
535
        } else {
536
            self._seekTimeRequested = time
537
        }
538
    }
539
 
540
    /// Updates the playback time to the specified time bound.
541
    ///
542
    /// - Parameters:
543
    ///   - time: The time to switch to move the playback.
544
    ///   - toleranceBefore: The tolerance allowed before time.
545
    ///   - toleranceAfter: The tolerance allowed after time.
546
    ///   - completionHandler: call block handler after seeking
547
    open func seekToTime(to time: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime, completionHandler: ((Bool) -> Swift.Void)? = nil) {
548
        if let playerItem = self._playerItem {
549
            return playerItem.seek(to: time, toleranceBefore: toleranceBefore, toleranceAfter: toleranceAfter, completionHandler: completionHandler)
550
        }
551
    }
552
 
553
    /// Captures a snapshot of the current Player asset.
554
    ///
555
    /// - Parameter completionHandler: Returns a UIImage of the requested video frame. (Great for thumbnails!)
556
    open func takeSnapshot(completionHandler: ((_ image: UIImage?, _ error: Error?) -> Void)? ) {
557
        guard let asset = self._playerItem?.asset else {
558
            DispatchQueue.main.async {
559
                completionHandler?(nil, nil)
560
            }
561
            return
562
        }
563
 
564
        let imageGenerator = AVAssetImageGenerator(asset: asset)
565
        imageGenerator.appliesPreferredTrackTransform = true
566
 
567
        let currentTime = self._playerItem?.currentTime() ?? CMTime.zero
568
 
569
        imageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: currentTime)]) { (requestedTime, image, actualTime, result, error) in
570
            guard let image = image else {
571
                DispatchQueue.main.async {
572
                    completionHandler?(nil, error)
573
                }
574
                return
575
            }
576
 
577
            switch result {
578
            case .succeeded:
579
                let uiimage = UIImage(cgImage: image)
580
                DispatchQueue.main.async {
581
                    completionHandler?(uiimage, nil)
582
                }
583
                break
584
            case .failed, .cancelled:
585
                fallthrough
586
            @unknown default:
587
                DispatchQueue.main.async {
588
                    completionHandler?(nil, nil)
589
                }
590
                break
591
            }
592
        }
593
    }
594
 
595
}
596
 
597
// MARK: - loading funcs
598
 
599
extension Player {
600
 
601
    fileprivate func setup(url: URL) {
602
        guard isViewLoaded else { return }
603
 
604
        // ensure everything is reset beforehand
605
        if self.playbackState == .playing {
606
            self.pause()
607
        }
608
 
609
        // Reset autoplay flag since a new url is set.
610
        self._hasAutoplayActivated = false
611
        if self.autoplay {
612
            self.playbackState = .playing
613
        } else {
614
            self.playbackState = .stopped
615
        }
616
 
617
        self.setupPlayerItem(nil)
618
 
619
        let asset = AVURLAsset(url: url, options: .none)
620
        self.setupAsset(asset)
621
    }
622
 
623
    fileprivate func setupAsset(_ asset: AVAsset, loadableKeys: [String] = ["tracks", "playable", "duration"]) {
624
        guard isViewLoaded else { return }
625
 
626
        if self.playbackState == .playing {
627
            self.pause()
628
        }
629
 
630
        self.bufferingState = .unknown
631
 
632
        self._asset = asset
633
 
634
        self._asset?.loadValuesAsynchronously(forKeys: loadableKeys, completionHandler: { () -> Void in
635
            guard let asset = self._asset else {
636
                return
637
            }
638
 
639
            for key in loadableKeys {
640
                var error: NSError? = nil
641
                let status = asset.statusOfValue(forKey: key, error: &error)
642
                if status == .failed {
643
                    self.playbackState = .failed
644
                    self.executeClosureOnMainQueueIfNecessary {
645
                        self.playerDelegate?.player(self, didFailWithError: error)
646
                    }
647
                    return
648
                }
649
            }
650
 
651
            if !asset.isPlayable {
652
                self.playbackState = .failed
653
                self.executeClosureOnMainQueueIfNecessary {
654
                    self.playerDelegate?.player(self, didFailWithError: PlayerError.failed)
655
                }
656
                return
657
            }
658
 
659
            let playerItem = AVPlayerItem(asset:asset)
660
            self.setupPlayerItem(playerItem)
661
        })
662
    }
663
 
664
    fileprivate func setupPlayerItem(_ playerItem: AVPlayerItem?) {
665
 
666
        self.removePlayerItemObservers()
667
 
668
        if let currentPlayerItem = self._playerItem {
669
            NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: currentPlayerItem)
670
            NotificationCenter.default.removeObserver(self, name: .AVPlayerItemFailedToPlayToEndTime, object: currentPlayerItem)
671
        }
672
 
673
        self._playerItem = playerItem
674
 
675
        self._playerItem?.preferredPeakBitRate = self.preferredPeakBitRate
676
        if #available(iOS 11.0, tvOS 11.0, *) {
677
            self._playerItem?.preferredMaximumResolution = self._preferredMaximumResolution
678
        }
679
 
680
        if let seek = self._seekTimeRequested, self._playerItem != nil {
681
            self._seekTimeRequested = nil
682
            self.seek(to: seek)
683
        }
684
 
685
        if let updatedPlayerItem = self._playerItem {
686
            self.addPlayerItemObservers()
687
            NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidPlayToEndTime(_:)), name: .AVPlayerItemDidPlayToEndTime, object: updatedPlayerItem)
688
            NotificationCenter.default.addObserver(self, selector: #selector(playerItemFailedToPlayToEndTime(_:)), name: .AVPlayerItemFailedToPlayToEndTime, object: updatedPlayerItem)
689
        }
690
 
691
        self._avplayer.replaceCurrentItem(with: self._playerItem)
692
 
693
        // update new playerItem settings
694
        if self.playbackLoops {
695
            self._avplayer.actionAtItemEnd = .none
696
        } else {
697
            self._avplayer.actionAtItemEnd = .pause
698
        }
699
    }
700
 
701
}
702
 
703
// MARK: - NSNotifications
704
 
705
extension Player {
706
 
707
    // MARK: - UIApplication
708
 
709
    internal func addApplicationObservers() {
710
        NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil)
711
        NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationDidBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil)
712
        NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationDidEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
713
        NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
714
    }
715
 
716
    internal func removeApplicationObservers() {
717
        NotificationCenter.default.removeObserver(self)
718
    }
719
 
720
    // MARK: - AVPlayerItem handlers
721
 
722
    @objc internal func playerItemDidPlayToEndTime(_ aNotification: Notification) {
723
        self.executeClosureOnMainQueueIfNecessary {
724
            if self.playbackLoops {
725
                self.playbackDelegate?.playerPlaybackWillLoop(self)
726
                self._avplayer.seek(to: CMTime.zero)
727
                self._avplayer.play()
728
                self.playbackDelegate?.playerPlaybackDidLoop(self)
729
            } else if self.playbackFreezesAtEnd {
730
                self.stop()
731
            } else {
732
                self._avplayer.seek(to: CMTime.zero, completionHandler: { _ in
733
                    self.stop()
734
                })
735
            }
736
        }
737
    }
738
 
739
    @objc internal func playerItemFailedToPlayToEndTime(_ aNotification: Notification) {
740
        self.playbackState = .failed
741
    }
742
 
743
    // MARK: - UIApplication handlers
744
 
745
    @objc internal func handleApplicationWillResignActive(_ aNotification: Notification) {
746
        if self.playbackState == .playing && self.playbackPausesWhenResigningActive {
747
            self.pause()
748
        }
749
    }
750
 
751
    @objc internal func handleApplicationDidBecomeActive(_ aNotification: Notification) {
752
        if self.playbackState == .paused && self.playbackResumesWhenBecameActive {
753
            self.play()
754
        }
755
    }
756
 
757
    @objc internal func handleApplicationDidEnterBackground(_ aNotification: Notification) {
758
        if self.playbackState == .playing && self.playbackPausesWhenBackgrounded {
759
            self.pause()
760
        }
761
    }
762
 
763
    @objc internal func handleApplicationWillEnterForeground(_ aNoticiation: Notification) {
764
        if self.playbackState != .playing && self.playbackResumesWhenEnteringForeground {
765
            self.play()
766
        }
767
    }
768
 
769
}
770
 
771
// MARK: - KVO
772
 
773
extension Player {
774
 
775
    // MARK: - AVPlayerItemObservers
776
 
777
    internal func addPlayerItemObservers() {
778
        guard let playerItem = self._playerItem else {
779
            return
780
        }
781
 
782
        self._playerItemObservers.append(playerItem.observe(\.isPlaybackBufferEmpty, options: [.new, .old]) { [weak self] (object, change) in
783
            if object.isPlaybackBufferEmpty {
784
                self?.bufferingState = .delayed
785
            }
786
 
787
            switch object.status {
788
            case .failed:
789
                self?.playbackState = PlaybackState.failed
790
            default:
791
                break
792
            }
793
        })
794
 
795
        self._playerItemObservers.append(playerItem.observe(\.isPlaybackLikelyToKeepUp, options: [.new, .old]) { [weak self] (object, change) in
796
            guard let strongSelf = self else {
797
                return
798
            }
799
 
800
            if object.isPlaybackLikelyToKeepUp {
801
                strongSelf.bufferingState = .ready
802
                if strongSelf.playbackState == .playing {
803
                    strongSelf.playFromCurrentTime()
804
                }
805
            }
806
 
807
            switch object.status {
808
            case .failed:
809
                strongSelf.playbackState = PlaybackState.failed
810
                break
811
            default:
812
                break
813
            }
814
        })
815
 
816
        self._playerItemObservers.append(playerItem.observe(\.loadedTimeRanges, options: [.new, .old]) { [weak self] (object, change) in
817
            guard let strongSelf = self else {
818
                return
819
            }
820
 
821
            let timeRanges = object.loadedTimeRanges
822
            if let timeRange = timeRanges.first?.timeRangeValue {
823
                let bufferedTime = CMTimeGetSeconds(CMTimeAdd(timeRange.start, timeRange.duration))
824
                if strongSelf._lastBufferTime != bufferedTime {
825
                    strongSelf._lastBufferTime = bufferedTime
826
                    strongSelf.executeClosureOnMainQueueIfNecessary {
827
                        strongSelf.playerDelegate?.playerBufferTimeDidChange(bufferedTime)
828
                    }
829
                }
830
            }
831
 
832
            let currentTime = CMTimeGetSeconds(object.currentTime())
833
            let passedTime = strongSelf._lastBufferTime <= 0 ? currentTime : (strongSelf._lastBufferTime - currentTime)
834
 
835
            if (passedTime >= strongSelf.bufferSizeInSeconds ||
836
                strongSelf._lastBufferTime == strongSelf.maximumDuration ||
837
                timeRanges.first == nil) &&
838
                strongSelf.playbackState == .playing {
839
                strongSelf.play()
840
            }
841
        })
842
    }
843
 
844
    internal func removePlayerItemObservers() {
845
        for observer in self._playerItemObservers {
846
            observer.invalidate()
847
        }
848
        self._playerItemObservers.removeAll()
849
    }
850
 
851
    // MARK: - AVPlayerLayerObservers
852
 
853
    internal func addPlayerLayerObservers() {
854
        self._playerLayerObserver = self._playerView.playerLayer.observe(\.isReadyForDisplay, options: [.new, .old]) { [weak self] (object, change) in
855
            self?.executeClosureOnMainQueueIfNecessary {
856
                if let strongSelf = self {
857
                    strongSelf.playerDelegate?.playerReady(strongSelf)
858
                }
859
            }
860
        }
861
    }
862
 
863
    internal func removePlayerLayerObservers() {
864
        self._playerLayerObserver?.invalidate()
865
        self._playerLayerObserver = nil
866
    }
867
 
868
    // MARK: - AVPlayerObservers
869
 
870
    internal func addPlayerObservers() {
871
        self._playerTimeObserver = self._avplayer.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 100), queue: DispatchQueue.main, using: { [weak self] timeInterval in
872
            guard let strongSelf = self else {
873
                return
874
            }
875
            strongSelf.playbackDelegate?.playerCurrentTimeDidChange(strongSelf)
876
        })
877
 
878
        if #available(iOS 10.0, tvOS 10.0, *) {
879
            self._playerObservers.append(self._avplayer.observe(\.timeControlStatus, options: [.new, .old]) { [weak self] (object, change) in
880
                switch object.timeControlStatus {
881
                case .paused:
882
                    self?.playbackState = .paused
883
                case .playing:
884
                    self?.playbackState = .playing
885
                case .waitingToPlayAtSpecifiedRate:
886
                    fallthrough
887
                @unknown default:
888
                    break
889
                }
890
            })
891
        }
892
 
893
    }
894
 
895
    internal func removePlayerObservers() {
896
        if let observer = self._playerTimeObserver {
897
            self._avplayer.removeTimeObserver(observer)
898
        }
899
        for observer in self._playerObservers {
900
            observer.invalidate()
901
        }
902
        self._playerObservers.removeAll()
903
    }
904
 
905
}
906
 
907
// MARK: - queues
908
 
909
extension Player {
910
 
911
    internal func executeClosureOnMainQueueIfNecessary(withClosure closure: @escaping () -> Void) {
912
        if Thread.isMainThread {
913
            closure()
914
        } else {
915
            DispatchQueue.main.async(execute: closure)
916
        }
917
    }
918
 
919
}
920
 
921
// MARK: - PlayerView
922
 
923
public class PlayerView: UIView {
924
 
925
    // MARK: - overrides
926
 
927
    public override class var layerClass: AnyClass {
928
        get {
929
            return AVPlayerLayer.self
930
        }
931
    }
932
 
933
    // MARK: - internal properties
934
 
935
    internal var playerLayer: AVPlayerLayer {
936
        get {
937
            return self.layer as! AVPlayerLayer
938
        }
939
    }
940
 
941
    internal var player: AVPlayer? {
942
        get {
943
            return self.playerLayer.player
944
        }
945
        set {
946
            self.playerLayer.player = newValue
947
            self.playerLayer.isHidden = (self.playerLayer.player == nil)
948
        }
949
    }
950
 
951
    // MARK: - public properties
952
 
953
    public var playerBackgroundColor: UIColor? {
954
        get {
955
            if let cgColor = self.playerLayer.backgroundColor {
956
                return UIColor(cgColor: cgColor)
957
            }
958
            return nil
959
        }
960
        set {
961
            self.playerLayer.backgroundColor = newValue?.cgColor
962
        }
963
    }
964
 
965
    public var playerFillMode: Player.FillMode {
966
        get {
967
            return self.playerLayer.videoGravity
968
        }
969
        set {
970
            self.playerLayer.videoGravity = newValue
971
        }
972
    }
973
 
974
    public var isReadyForDisplay: Bool {
975
        get {
976
            return self.playerLayer.isReadyForDisplay
977
        }
978
    }
979
 
980
    // MARK: - object lifecycle
981
 
982
    public override init(frame: CGRect) {
983
        super.init(frame: frame)
984
        self.playerLayer.isHidden = true
985
        self.playerFillMode = .resizeAspect
986
    }
987
 
988
    required public init?(coder aDecoder: NSCoder) {
989
        super.init(coder: aDecoder)
990
        self.playerLayer.isHidden = true
991
        self.playerFillMode = .resizeAspect
992
    }
993
 
994
    deinit {
995
        self.player?.pause()
996
        self.player = nil
997
    }
998
 
999
}