@picsa/capacitor-video-player 7.1.0 → 7.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CapacitorVideoPlayer.podspec +17 -17
  2. package/LICENSE +21 -21
  3. package/android/build.gradle +71 -71
  4. package/android/src/main/AndroidManifest.xml +3 -3
  5. package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/CapacitorVideoPlayer.java +71 -71
  6. package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/CapacitorVideoPlayerPlugin.java +1256 -1256
  7. package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/FullscreenExoPlayerFragment.java +1591 -1595
  8. package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/Notifications/MyRunnable.java +21 -21
  9. package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/Notifications/NotificationCenter.java +61 -61
  10. package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/PickerVideo/AdapterVideoList.java +47 -47
  11. package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/PickerVideo/ModelVideo.java +49 -49
  12. package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/PickerVideo/PickerVideoFragment.java +119 -119
  13. package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/PickerVideo/VideoRecyclerViewHolder.java +65 -65
  14. package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/Utilities/FilesUtils.java +101 -101
  15. package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/Utilities/FragmentUtils.java +32 -32
  16. package/android/src/main/res/drawable/bg_round_rect_white_50.xml +9 -9
  17. package/android/src/main/res/drawable/bg_rounded_rectangle_white_corner_rounded.xml +10 -10
  18. package/android/src/main/res/drawable/exo_close_btn.xml +11 -11
  19. package/android/src/main/res/drawable/gradient_transparent_middle.xml +12 -12
  20. package/android/src/main/res/drawable/ic_arrow_left.xml +5 -5
  21. package/android/src/main/res/drawable/ic_baseline_lq.xml +7 -7
  22. package/android/src/main/res/drawable/ic_exo_icon_fastforward.xml +35 -35
  23. package/android/src/main/res/drawable/ic_exo_icon_pause.xml +26 -26
  24. package/android/src/main/res/drawable/ic_exo_icon_play.xml +36 -36
  25. package/android/src/main/res/drawable/ic_exo_icon_rewind.xml +35 -35
  26. package/android/src/main/res/drawable/ic_expand.xml +5 -5
  27. package/android/src/main/res/drawable/ic_fit.xml +5 -5
  28. package/android/src/main/res/drawable/ic_image_background.xml +12 -12
  29. package/android/src/main/res/drawable/ic_img_16_9_background.xml +10 -10
  30. package/android/src/main/res/drawable/ic_img_9_16_background.xml +10 -10
  31. package/android/src/main/res/drawable/ic_outline_lock.xml +5 -5
  32. package/android/src/main/res/drawable/ic_outline_lock_open.xml +5 -5
  33. package/android/src/main/res/drawable/ic_pip_white.xml +5 -5
  34. package/android/src/main/res/drawable/ic_views.xml +18 -18
  35. package/android/src/main/res/drawable/ic_zoom.xml +5 -5
  36. package/android/src/main/res/layout/bridge_layout_main.xml +15 -15
  37. package/android/src/main/res/layout/exo_playback_control_view.xml +289 -289
  38. package/android/src/main/res/layout/exoplayer_layout_youtube.xml +361 -361
  39. package/android/src/main/res/layout/fragment_fs_exoplayer.xml +50 -50
  40. package/android/src/main/res/layout/fragment_picker_video.xml +21 -21
  41. package/android/src/main/res/layout/row_video.xml +76 -76
  42. package/android/src/main/res/values/colors.xml +14 -14
  43. package/android/src/main/res/values/strings.xml +3 -3
  44. package/android/src/main/res/values/styles.xml +3 -3
  45. package/dist/docs.json +14 -14
  46. package/ios/Plugin/CapacitorVideoPlayer.swift +63 -63
  47. package/ios/Plugin/CapacitorVideoPlayerPlugin.h +10 -10
  48. package/ios/Plugin/CapacitorVideoPlayerPlugin.m +22 -22
  49. package/ios/Plugin/CapacitorVideoPlayerPlugin.swift +631 -631
  50. package/ios/Plugin/Extensions/CapacitorVideoPlayerPlugin.AVPlayerViewController.swift +59 -59
  51. package/ios/Plugin/Extensions/CapacitorVideoPlayerPlugin.File.swift +126 -126
  52. package/ios/Plugin/Extensions/CapacitorVideoPlayerPlugin.Fullscreen.swift +94 -94
  53. package/ios/Plugin/Extensions/CapacitorVideoPlayerPlugin.Notifications.swift +140 -140
  54. package/ios/Plugin/Extensions/CapacitorVideoPlayerPlugin.Observers.swift +180 -180
  55. package/ios/Plugin/Info.plist +24 -24
  56. package/ios/Plugin/VideoPicker/VideoHelper.swift +127 -127
  57. package/ios/Plugin/VideoPicker/VideoPickerViewController.swift +144 -144
  58. package/ios/Plugin/VideoPlayer/Extensions/AVURLAsset.swift +18 -18
  59. package/ios/Plugin/VideoPlayer/Extensions/AllOrientationAVPlayerController.swift +12 -12
  60. package/ios/Plugin/VideoPlayer/Extensions/CMTime.swift +23 -23
  61. package/ios/Plugin/VideoPlayer/Extensions/LandscapeAVPlayerController.swift +12 -12
  62. package/ios/Plugin/VideoPlayer/Extensions/Notification.Name.swift +18 -18
  63. package/ios/Plugin/VideoPlayer/Extensions/PortraitAVPlayerController.swift +12 -12
  64. package/ios/Plugin/VideoPlayer/Extensions/View.swift +54 -54
  65. package/ios/Plugin/VideoPlayer/FullScreenVideoPlayerView.swift +601 -601
  66. package/ios/Plugin/VideoPlayer/Models/Video.swift +28 -28
  67. package/ios/Plugin/VideoPlayer/Models/VideoModel.swift +42 -42
  68. package/package.json +92 -92
  69. package/readme.md +227 -190
@@ -1,601 +1,601 @@
1
- //
2
- // FullScreenVideoPlayerView.swift
3
- // Plugin
4
- //
5
- // Created by Quéau Jean Pierre on 13/01/2020.
6
- // Copyright © 2021 Max Lynch. All rights reserved.
7
- //
8
-
9
- import UIKit
10
- import AVKit
11
- import MediaPlayer
12
-
13
- // swiftlint:disable file_length
14
- // swiftlint:disable type_body_length
15
- open class FullScreenVideoPlayerView: UIView {
16
- private var _url: URL
17
- private var _isReadyToPlay: Bool = false
18
- private var _videoId: String = "fullscreen"
19
- private var _currentTime: Double = 0
20
- private var _duration: Double = 0
21
- private var _isLoaded: [String: Bool] = [:]
22
- private var _isBufferEmpty: [String: Bool] = [:]
23
- private var _exitOnEnd: Bool = true
24
- private var _loopOnEnd: Bool = false
25
- private var _pipEnabled: Bool = true
26
- private var _firstReadyToPlay: Bool = true
27
- private var _stUrl: URL?
28
- private var _stLanguage: String?
29
- private var _stHeaders: [String: String]?
30
- private var _stOptions: [String: Any]?
31
- private var _videoRate: Float
32
- private var _showControls: Bool = true
33
- private var _displayMode: String = "all"
34
- private var _title: String?
35
- private var _smallTitle: String?
36
- private var _artwork: String?
37
-
38
- var player: AVPlayer?
39
- var videoPlayer: AVPlayerViewController
40
- var videoAsset: AVURLAsset
41
- var playerItem: AVPlayerItem?
42
- var isPlaying: Bool
43
- var itemBufferObserver: NSKeyValueObservation?
44
- var itemStatusObserver: NSKeyValueObservation?
45
- var playerRateObserver: NSKeyValueObservation?
46
- var videoPlayerFrameObserver: NSKeyValueObservation?
47
- var videoPlayerMoveObserver: NSKeyValueObservation?
48
- var periodicTimeObserver: Any?
49
-
50
- init(url: URL, rate: Float, playerId: String, exitOnEnd: Bool,
51
- loopOnEnd: Bool, pipEnabled: Bool, showControls: Bool,
52
- displayMode: String, stUrl: URL?, stLanguage: String?,
53
- stHeaders: [String: String]?, stOptions: [String: Any]?,
54
- title: String?, smallTitle: String?, artwork: String?) {
55
- //self._videoPath = videoPath
56
- self._url = url
57
- self._stUrl = stUrl
58
- self._stLanguage = stLanguage
59
- self._stOptions = stOptions
60
- self._exitOnEnd = exitOnEnd
61
- self._loopOnEnd = loopOnEnd
62
- self._pipEnabled = pipEnabled
63
- self._videoId = playerId
64
- self._videoRate = rate
65
- self._stHeaders = stHeaders
66
- self._displayMode = displayMode
67
- self.videoPlayer = AllOrientationAVPlayerController()
68
- if displayMode == "landscape" {
69
- self.videoPlayer = LandscapeAVPlayerController()
70
- }
71
- if displayMode == "portrait" {
72
- self.videoPlayer = PortraitAVPlayerController()
73
- }
74
- self._showControls = showControls
75
- self._title = title
76
- self._smallTitle = smallTitle
77
- self._artwork = artwork
78
-
79
- if let headers = self._stHeaders {
80
- self.videoAsset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
81
- } else {
82
- self.videoAsset = AVURLAsset(url: url)
83
- }
84
-
85
- self.isPlaying = false
86
- super.init(frame: .zero)
87
- self.initialize()
88
- self.addObservers()
89
- }
90
-
91
- // swiftlint:disable function_body_length
92
- // swiftlint:disable cyclomatic_complexity
93
- private func initialize() {
94
- // Set SubTitles if any
95
- if var subTitleUrl = self._stUrl {
96
- //check if subtitle is .srt
97
- if subTitleUrl.pathExtension == "srt" {
98
- let vttUrl: URL = srtSubtitleToVtt(srtURL: subTitleUrl)
99
- self._stUrl = vttUrl
100
- subTitleUrl = vttUrl
101
- }
102
- var textStyle: [AVTextStyleRule] = []
103
- if let opt = self._stOptions {
104
- textStyle.append(contentsOf: self.setSubTitleStyle(options: opt))
105
- }
106
-
107
- let subTitleAsset = AVAsset(url: subTitleUrl)
108
- let composition = AVMutableComposition()
109
-
110
- if let videoTrack = composition.addMutableTrack(
111
- withMediaType: AVMediaType.video,
112
- preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) {
113
- if let audioTrack = composition.addMutableTrack(
114
- withMediaType: AVMediaType.audio,
115
- preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) {
116
- do {
117
- try videoTrack.insertTimeRange(
118
- CMTimeRangeMake(start: CMTime.zero,
119
- duration: self.videoAsset.duration),
120
- of: self.videoAsset.tracks(
121
- withMediaType: AVMediaType.video)[0],
122
- at: CMTime.zero)
123
- // if video has an audio track
124
- if self.videoAsset.tracks.count > 0 {
125
- let clipAudioTrack = self.videoAsset.tracks(
126
- withMediaType: AVMediaType.audio)[0]
127
- try audioTrack.insertTimeRange(CMTimeRangeMake(
128
- start: CMTime.zero,
129
- duration: self.videoAsset.duration),
130
- of: clipAudioTrack, at: CMTime.zero)
131
- }
132
- //Adds subtitle track
133
- if let subtitleTrack = composition.addMutableTrack(
134
- withMediaType: .text,
135
- preferredTrackID: kCMPersistentTrackID_Invalid) {
136
- do {
137
- let duration = self.videoAsset.duration
138
- try subtitleTrack.insertTimeRange(
139
- CMTimeRangeMake(start: CMTime.zero,
140
- duration: duration),
141
- of: subTitleAsset.tracks(
142
- withMediaType: .text)[0],
143
- at: CMTime.zero)
144
-
145
- self.playerItem = AVPlayerItem(asset: composition)
146
- self.playerItem?.textStyleRules = textStyle
147
-
148
- } catch {
149
- self.playerItem = AVPlayerItem(asset: self.videoAsset)
150
- }
151
- } else {
152
- self.playerItem = AVPlayerItem(asset: self.videoAsset)
153
- }
154
- } catch {
155
- self.playerItem = AVPlayerItem(asset: self.videoAsset)
156
- }
157
- } else {
158
- self.playerItem = AVPlayerItem(asset: self.videoAsset)
159
- }
160
- } else {
161
- self.playerItem = AVPlayerItem(asset: self.videoAsset)
162
- }
163
- } else {
164
- self.playerItem = AVPlayerItem(asset: self.videoAsset)
165
- }
166
- self.player = AVPlayer(playerItem: self.playerItem)
167
- self.player?.currentItem?.audioTimePitchAlgorithm = .timeDomain
168
- if !self._showControls {
169
- self.videoPlayer.showsPlaybackControls = false
170
- }
171
- self.videoPlayer.player = self.player
172
- self.videoPlayer.updatesNowPlayingInfoCenter = false
173
- if #available(iOS 13.0, *) {
174
- self.videoPlayer.isModalInPresentation = true
175
- } else {
176
- // Fallback on earlier versions
177
- }
178
- self.videoPlayer.allowsPictureInPicturePlayback = false
179
- if isPIPModeAvailable && self._pipEnabled {
180
- self.videoPlayer.allowsPictureInPicturePlayback = true
181
- }
182
-
183
- self._isLoaded.updateValue(false, forKey: self._videoId)
184
-
185
- }
186
- // swiftlint:enable cyclomatic_complexity
187
- // swiftlint:enable function_body_length
188
-
189
- private func setSubTitleStyle(options: [String: Any]) -> [AVTextStyleRule] {
190
- var styles: [AVTextStyleRule] = []
191
- var backColor: [Float] = [1.0, 0.0, 0.0, 0.0]
192
- if let bckCol = options["backgroundColor"] as? String {
193
- let color = self.getColorFromRGBA(rgba: bckCol)
194
- backColor = color.count > 0 ? color : backColor
195
- }
196
- if let textStyle: AVTextStyleRule = AVTextStyleRule(textMarkupAttributes: [
197
- kCMTextMarkupAttribute_CharacterBackgroundColorARGB as String:
198
- backColor
199
- ]) {
200
- styles.append(textStyle)
201
- }
202
-
203
- var foreColor: [Float] = [1.0, 1.0, 1.0, 1.0]
204
- if let foreCol = options["foregroundColor"] as? String {
205
- let color = self.getColorFromRGBA(rgba: foreCol)
206
- foreColor = color.count > 0 ? color : foreColor
207
- }
208
- if let textStyle1: AVTextStyleRule = AVTextStyleRule(textMarkupAttributes: [
209
- kCMTextMarkupAttribute_ForegroundColorARGB as String: foreColor
210
- ]) {
211
- styles.append(textStyle1)
212
- }
213
- var ftSize = 160
214
- if let pixSize = options["fontSize"] as? Int {
215
- ftSize = pixSize * 10
216
- }
217
- if let textStyle2: AVTextStyleRule = AVTextStyleRule(textMarkupAttributes: [
218
- kCMTextMarkupAttribute_RelativeFontSize as String: ftSize,
219
- kCMTextMarkupAttribute_CharacterEdgeStyle as String: kCMTextMarkupCharacterEdgeStyle_None
220
- ]) {
221
- styles.append(textStyle2)
222
- }
223
- return styles
224
- }
225
- // MARK: - Add Observers
226
-
227
- // swiftlint:disable function_body_length
228
- // swiftlint:disable cyclomatic_complexity
229
- private func addObservers() {
230
-
231
- self.itemStatusObserver = self.playerItem?
232
- .observe(\.status, options: [.new, .old],
233
- changeHandler: {(playerItem, _) in
234
- // Switch over the status
235
- switch playerItem.status {
236
- case .readyToPlay:
237
- // Player item is ready to play.
238
- if self._firstReadyToPlay {
239
- self._isLoaded.updateValue(true, forKey: self._videoId)
240
- self._isReadyToPlay = true
241
- isVideoEnded = false
242
- if let item = self.playerItem {
243
- self._currentTime = CMTimeGetSeconds(item.currentTime())
244
- }
245
- let vId: [String: Any] = ["fromPlayerId": self._videoId, "currentTime": self._currentTime,
246
- "videoRate": self._videoRate]
247
- NotificationCenter.default.post(name: .playerItemReady, object: nil, userInfo: vId)
248
- self._firstReadyToPlay = false
249
-
250
- self.setNowPlayingInfo()
251
- self.setRemoteCommandCenter()
252
- self.setNowPlayingImage()
253
- }
254
- case .failed:
255
- print("failing to load")
256
- self._isLoaded.updateValue(false, forKey: self._videoId)
257
- case .unknown:
258
- // Player item is not yet ready.
259
- print("playerItem not yet ready")
260
-
261
- @unknown default:
262
- print("playerItem Error \(String(describing: self.playerItem?.error))")
263
- }
264
-
265
- })
266
-
267
- self.itemBufferObserver = self.playerItem?
268
- .observe(\.isPlaybackBufferEmpty,
269
- options: [.new, .old], changeHandler: {(playerItem, _) in
270
- let empty: Bool = ((self.playerItem?.isPlaybackBufferEmpty) != nil)
271
- if empty {
272
- self._isBufferEmpty.updateValue(true, forKey: self._videoId)
273
- } else {
274
- self._isBufferEmpty.updateValue(false, forKey: self._videoId)
275
- }
276
- })
277
- self.playerRateObserver = self.player?
278
- .observe(\.rate, options: [.new, .old], changeHandler: {(player, _) in
279
- let rate: Float = player.rate
280
- if let item = self.playerItem {
281
- self._currentTime = CMTimeGetSeconds(item.currentTime())
282
- self._duration = CMTimeGetSeconds(item.duration)
283
- }
284
- let vId: [String: Any] = [
285
- "fromPlayerId": self._videoId,
286
- "currentTime": self._currentTime,
287
- "videoRate": self._videoRate
288
- ]
289
-
290
- if !(self._isLoaded[self._videoId] ?? true) {
291
- print("AVPlayer Rate for player \(self._videoId): Loading")
292
- } else if rate > 0 && self._isReadyToPlay {
293
- if rate != self._videoRate {
294
- player.rate = self._videoRate
295
- }
296
-
297
- self.isPlaying = true
298
- NotificationCenter.default.post(name: .playerItemPlay, object: nil, userInfo: vId)
299
- } else if rate == 0 && !isVideoEnded && abs(self._currentTime - self._duration) < 0.2 {
300
- self.isPlaying = false
301
- player.seek(to: CMTime.zero)
302
- self._currentTime = 0
303
- if /*!isInPIPMode && */self._exitOnEnd {
304
- isVideoEnded = true
305
- NotificationCenter.default.post(name: .playerItemEnd, object: nil, userInfo: vId)
306
- } else {
307
- if self._loopOnEnd {
308
- self.play()
309
- }
310
- }
311
- } else if rate == 0 {
312
- if !isInPIPMode && !isInBackgroundMode && !isRateZero {
313
- self.isPlaying = false
314
- if !self.videoPlayer.isBeingDismissed {
315
- print("AVPlayer Rate for player \(self._videoId): Paused")
316
- NotificationCenter.default.post(name: .playerItemPause, object: nil, userInfo: vId)
317
- }
318
- } else {
319
- isRateZero = true
320
- }
321
- } else if self._isBufferEmpty[self._videoId] ?? true {
322
- print("AVPlayer Rate for player \(self._videoId): Buffer Empty Loading")
323
- }
324
- })
325
- self.videoPlayerFrameObserver = self.videoPlayer
326
- .observe(\.view.frame, options: [.new, .old],
327
- changeHandler: {(_, _) in
328
- if !isInPIPMode {
329
- if self.videoPlayer.isBeingDismissed && !isVideoEnded {
330
- NotificationCenter.default.post(name: .playerFullscreenDismiss, object: nil)
331
- }
332
- }
333
-
334
- })
335
- self.videoPlayerMoveObserver = self.videoPlayer
336
- .observe(\.view.center, options: [.new, .old],
337
- changeHandler: {(_, _) in
338
- if !isInPIPMode {
339
- if self.videoPlayer.isBeingDismissed && !isVideoEnded {
340
- NotificationCenter.default.post(name: .playerFullscreenDismiss, object: nil)
341
- }
342
-
343
- }
344
-
345
- })
346
- }
347
-
348
- // swiftlint:enable function_body_length
349
- // swiftlint:enable cyclomatic_complexity
350
-
351
- // MARK: - Remove Observers
352
-
353
- func removeObservers() {
354
- self.itemStatusObserver?.invalidate()
355
- self.itemBufferObserver?.invalidate()
356
- self.playerRateObserver?.invalidate()
357
- self.videoPlayerFrameObserver?.invalidate()
358
- }
359
-
360
- // MARK: - Required init
361
-
362
- public required init?(coder: NSCoder) {
363
- fatalError("init(coder:) has not been implemented")
364
- }
365
- // MARK: - Set-up Public functions
366
-
367
- @objc func play() {
368
- self.isPlaying = true
369
- self.player?.play()
370
- self.player?.rate = _videoRate
371
- }
372
- @objc func pause() {
373
- self.isPlaying = false
374
- self.player?.pause()
375
- }
376
- @objc func didFinishPlaying() -> Bool {
377
- return isVideoEnded
378
- }
379
- @objc func getDuration() -> Double {
380
- return Double(CMTimeGetSeconds(self.videoAsset.duration))
381
- }
382
- @objc func getCurrentTime() -> Double {
383
- return self._currentTime
384
- }
385
- // This func will return the updated currentTime of player item
386
- // getCurrentTime() is only updated when player plays, pauses, seek, etc
387
- // the function is only used in playerFullscreenDismiss() Notification
388
- public func getRealCurrentTime() -> Double {
389
- if let item = self.playerItem {
390
- let currentTime = CMTimeGetSeconds(item.currentTime())
391
- return currentTime
392
- } else {
393
- return 0
394
- }
395
- }
396
- @objc func setCurrentTime(time: Double) {
397
- let seekTime: CMTime = CMTimeMake(value: Int64(time*1000), timescale: 1000)
398
- self.player?.seek(to: seekTime)
399
- self._currentTime = time
400
- }
401
- @objc func getVolume() -> Float {
402
- if let player = self.player {
403
- return player.volume
404
- } else {
405
- return 1.0
406
- }
407
- }
408
- @objc func setVolume(volume: Float) {
409
- self.player?.volume = volume
410
- }
411
- @objc func getRate() -> Float {
412
- return _videoRate
413
- }
414
-
415
- @objc func setRate(rate: Float) {
416
- _videoRate = rate
417
- }
418
- @objc func getMuted() -> Bool {
419
- return ((self.player?.isMuted) != nil)
420
- }
421
- @objc func setMuted(muted: Bool) {
422
- self.player?.isMuted = muted
423
- }
424
-
425
- private func getColorFromRGBA(rgba: String) -> [Float] {
426
- if let oPar = rgba.firstIndex(of: "(") {
427
- if let cPar = rgba.firstIndex(of: ")") {
428
- let strColor = rgba[rgba.index(after: oPar)..<cPar]
429
- let array = strColor.components(separatedBy: ",")
430
- if array.count == 4 {
431
- var retArray: [Float] = []
432
- retArray.append((array[3]
433
- .trimmingCharacters(in: .whitespaces) as NSString)
434
- .floatValue)
435
- retArray.append((array[0]
436
- .trimmingCharacters(in: .whitespaces) as NSString)
437
- .floatValue / 255)
438
- retArray.append((array[1]
439
- .trimmingCharacters(in: .whitespaces) as NSString)
440
- .floatValue / 255)
441
- retArray.append((array[2]
442
- .trimmingCharacters(in: .whitespaces) as NSString)
443
- .floatValue / 255)
444
- return retArray
445
- } else {
446
- return []
447
- }
448
- } else {
449
- return []
450
- }
451
- } else {
452
- return []
453
- }
454
- }
455
-
456
- private func srtSubtitleToVtt(srtURL: URL) -> URL {
457
- guard let cachesURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
458
- fatalError("Couldn't get caches directory")
459
- }
460
- let vttFileName = UUID().uuidString + ".vtt"
461
- let vttURL = cachesURL.appendingPathComponent(vttFileName)
462
- let session = URLSession(configuration: .default)
463
- let vttFolderURL = vttURL.deletingLastPathComponent()
464
- do {
465
- try FileManager.default.createDirectory(at: vttFolderURL, withIntermediateDirectories: true, attributes: nil)
466
- } catch let error {
467
- print("Creating folder error: ", error)
468
- }
469
- let task = session.dataTask(with: srtURL) { (data, _, error) in
470
- guard let data = data, error == nil else {
471
- print("Download failed: \(error?.localizedDescription ?? "ukn")")
472
- return
473
- }
474
- do {
475
- let tempSRTURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("subtitulos.srt")
476
- try data.write(to: tempSRTURL)
477
- let srtContent = try String(contentsOf: tempSRTURL, encoding: .utf8)
478
- let vttContent = srtContent.replacingOccurrences(of: ",", with: ".")
479
- let vttString = "WEBVTT\n\n" + vttContent
480
- try vttString.write(toFile: vttURL.path, atomically: true, encoding: .utf8)
481
- try FileManager.default.removeItem(at: tempSRTURL)
482
-
483
- } catch let error {
484
- print("Processing subs error: \(error)")
485
- exit(1)
486
- }
487
-
488
- }
489
-
490
- task.resume()
491
- return vttURL
492
- }
493
-
494
- func setRemoteCommandCenter() {
495
- let rcc = MPRemoteCommandCenter.shared()
496
-
497
- rcc.playCommand.isEnabled = true
498
- rcc.playCommand.addTarget {event in
499
- self.play()
500
- return .success
501
- }
502
- rcc.pauseCommand.isEnabled = true
503
- rcc.pauseCommand.addTarget {event in
504
- self.pause()
505
- return .success
506
- }
507
- rcc.changePlaybackPositionCommand.isEnabled = true
508
- rcc.changePlaybackPositionCommand.addTarget {event in
509
- let seconds = (event as? MPChangePlaybackPositionCommandEvent)?.positionTime ?? 0
510
- let time = CMTime(seconds: seconds, preferredTimescale: 1)
511
- self.player?.seek(to: time)
512
- return .success
513
- }
514
- rcc.skipForwardCommand.isEnabled = true
515
- rcc.skipForwardCommand.addTarget {event in
516
- if let player = self.player, let currentItem = player.currentItem {
517
- let currentTime = CMTimeGetSeconds(currentItem.currentTime()) + 10
518
- self.player?.seek(to: CMTimeMakeWithSeconds(currentTime, preferredTimescale: 1))
519
- return .success
520
- } else {
521
- return .commandFailed
522
- }
523
- }
524
- rcc.skipBackwardCommand.isEnabled = true
525
- rcc.skipBackwardCommand.addTarget {event in
526
- if let player = self.player, let currentItem = player.currentItem {
527
- let currentTime = CMTimeGetSeconds(currentItem.currentTime()) - 10
528
- self.player?.seek(to: CMTimeMakeWithSeconds(currentTime, preferredTimescale: 1))
529
- return .success
530
- } else {
531
- return .commandFailed
532
- }
533
- }
534
-
535
- // Next and previous track buttons are disabled because we don't have more than 1 video
536
- rcc.nextTrackCommand.isEnabled = false
537
- rcc.previousTrackCommand.isEnabled = false
538
- }
539
-
540
- func setNowPlayingImage() {
541
- if let artwork = self._artwork {
542
- let session = URLSession(configuration: .default)
543
- let image = URL(string: artwork)!
544
- let task = session.dataTask(with: image) { (data, response, error) in
545
- guard let imageData = data, error == nil else {
546
- print("Error while downloading the image: \(error?.localizedDescription ?? "")")
547
- return
548
- }
549
-
550
- let image = UIImage(data: imageData)
551
- DispatchQueue.main.async {
552
- var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
553
- nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image?.size ?? CGSize.zero, requestHandler: { _ in
554
- return image ?? UIImage()
555
- })
556
- MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
557
- }
558
- }
559
- task.resume()
560
- }
561
- }
562
-
563
- func setNowPlayingInfo() {
564
- var nowPlayingInfo = [String: Any]()
565
-
566
- if let title = self._title {
567
- nowPlayingInfo[MPMediaItemPropertyTitle] = title
568
- }
569
- if let smalltitle = self._smallTitle {
570
- nowPlayingInfo[MPMediaItemPropertyArtist] = smalltitle
571
- }
572
-
573
- nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = NSNumber(value: MPNowPlayingInfoMediaType.video.rawValue)
574
- MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
575
- UIApplication.shared.beginReceivingRemoteControlEvents()
576
- periodicTimeObserver = self.player?.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 1), queue: DispatchQueue.main) { [weak self] time in
577
- guard let self = self else { return }
578
-
579
- var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
580
- if let currentItem = self.player?.currentItem,
581
- let currentTime = self.player?.currentTime(),
582
- currentItem.status == .readyToPlay {
583
-
584
- let elapsedTime = CMTimeGetSeconds(currentTime)
585
- if currentItem.isPlaybackLikelyToKeepUp {
586
- nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = self.player?.rate
587
- } else {
588
- nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 0
589
- }
590
-
591
- nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = Float(elapsedTime)
592
- nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = currentItem.duration.seconds
593
-
594
- MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
595
- }
596
- }
597
- }
598
- }
599
-
600
- // swiftlint:enable type_body_length
601
- // swiftlint:enable file_length
1
+ //
2
+ // FullScreenVideoPlayerView.swift
3
+ // Plugin
4
+ //
5
+ // Created by Quéau Jean Pierre on 13/01/2020.
6
+ // Copyright © 2021 Max Lynch. All rights reserved.
7
+ //
8
+
9
+ import UIKit
10
+ import AVKit
11
+ import MediaPlayer
12
+
13
+ // swiftlint:disable file_length
14
+ // swiftlint:disable type_body_length
15
+ open class FullScreenVideoPlayerView: UIView {
16
+ private var _url: URL
17
+ private var _isReadyToPlay: Bool = false
18
+ private var _videoId: String = "fullscreen"
19
+ private var _currentTime: Double = 0
20
+ private var _duration: Double = 0
21
+ private var _isLoaded: [String: Bool] = [:]
22
+ private var _isBufferEmpty: [String: Bool] = [:]
23
+ private var _exitOnEnd: Bool = true
24
+ private var _loopOnEnd: Bool = false
25
+ private var _pipEnabled: Bool = true
26
+ private var _firstReadyToPlay: Bool = true
27
+ private var _stUrl: URL?
28
+ private var _stLanguage: String?
29
+ private var _stHeaders: [String: String]?
30
+ private var _stOptions: [String: Any]?
31
+ private var _videoRate: Float
32
+ private var _showControls: Bool = true
33
+ private var _displayMode: String = "all"
34
+ private var _title: String?
35
+ private var _smallTitle: String?
36
+ private var _artwork: String?
37
+
38
+ var player: AVPlayer?
39
+ var videoPlayer: AVPlayerViewController
40
+ var videoAsset: AVURLAsset
41
+ var playerItem: AVPlayerItem?
42
+ var isPlaying: Bool
43
+ var itemBufferObserver: NSKeyValueObservation?
44
+ var itemStatusObserver: NSKeyValueObservation?
45
+ var playerRateObserver: NSKeyValueObservation?
46
+ var videoPlayerFrameObserver: NSKeyValueObservation?
47
+ var videoPlayerMoveObserver: NSKeyValueObservation?
48
+ var periodicTimeObserver: Any?
49
+
50
+ init(url: URL, rate: Float, playerId: String, exitOnEnd: Bool,
51
+ loopOnEnd: Bool, pipEnabled: Bool, showControls: Bool,
52
+ displayMode: String, stUrl: URL?, stLanguage: String?,
53
+ stHeaders: [String: String]?, stOptions: [String: Any]?,
54
+ title: String?, smallTitle: String?, artwork: String?) {
55
+ //self._videoPath = videoPath
56
+ self._url = url
57
+ self._stUrl = stUrl
58
+ self._stLanguage = stLanguage
59
+ self._stOptions = stOptions
60
+ self._exitOnEnd = exitOnEnd
61
+ self._loopOnEnd = loopOnEnd
62
+ self._pipEnabled = pipEnabled
63
+ self._videoId = playerId
64
+ self._videoRate = rate
65
+ self._stHeaders = stHeaders
66
+ self._displayMode = displayMode
67
+ self.videoPlayer = AllOrientationAVPlayerController()
68
+ if displayMode == "landscape" {
69
+ self.videoPlayer = LandscapeAVPlayerController()
70
+ }
71
+ if displayMode == "portrait" {
72
+ self.videoPlayer = PortraitAVPlayerController()
73
+ }
74
+ self._showControls = showControls
75
+ self._title = title
76
+ self._smallTitle = smallTitle
77
+ self._artwork = artwork
78
+
79
+ if let headers = self._stHeaders {
80
+ self.videoAsset = AVURLAsset(url: url, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
81
+ } else {
82
+ self.videoAsset = AVURLAsset(url: url)
83
+ }
84
+
85
+ self.isPlaying = false
86
+ super.init(frame: .zero)
87
+ self.initialize()
88
+ self.addObservers()
89
+ }
90
+
91
+ // swiftlint:disable function_body_length
92
+ // swiftlint:disable cyclomatic_complexity
93
+ private func initialize() {
94
+ // Set SubTitles if any
95
+ if var subTitleUrl = self._stUrl {
96
+ //check if subtitle is .srt
97
+ if subTitleUrl.pathExtension == "srt" {
98
+ let vttUrl: URL = srtSubtitleToVtt(srtURL: subTitleUrl)
99
+ self._stUrl = vttUrl
100
+ subTitleUrl = vttUrl
101
+ }
102
+ var textStyle: [AVTextStyleRule] = []
103
+ if let opt = self._stOptions {
104
+ textStyle.append(contentsOf: self.setSubTitleStyle(options: opt))
105
+ }
106
+
107
+ let subTitleAsset = AVAsset(url: subTitleUrl)
108
+ let composition = AVMutableComposition()
109
+
110
+ if let videoTrack = composition.addMutableTrack(
111
+ withMediaType: AVMediaType.video,
112
+ preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) {
113
+ if let audioTrack = composition.addMutableTrack(
114
+ withMediaType: AVMediaType.audio,
115
+ preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) {
116
+ do {
117
+ try videoTrack.insertTimeRange(
118
+ CMTimeRangeMake(start: CMTime.zero,
119
+ duration: self.videoAsset.duration),
120
+ of: self.videoAsset.tracks(
121
+ withMediaType: AVMediaType.video)[0],
122
+ at: CMTime.zero)
123
+ // if video has an audio track
124
+ if self.videoAsset.tracks.count > 0 {
125
+ let clipAudioTrack = self.videoAsset.tracks(
126
+ withMediaType: AVMediaType.audio)[0]
127
+ try audioTrack.insertTimeRange(CMTimeRangeMake(
128
+ start: CMTime.zero,
129
+ duration: self.videoAsset.duration),
130
+ of: clipAudioTrack, at: CMTime.zero)
131
+ }
132
+ //Adds subtitle track
133
+ if let subtitleTrack = composition.addMutableTrack(
134
+ withMediaType: .text,
135
+ preferredTrackID: kCMPersistentTrackID_Invalid) {
136
+ do {
137
+ let duration = self.videoAsset.duration
138
+ try subtitleTrack.insertTimeRange(
139
+ CMTimeRangeMake(start: CMTime.zero,
140
+ duration: duration),
141
+ of: subTitleAsset.tracks(
142
+ withMediaType: .text)[0],
143
+ at: CMTime.zero)
144
+
145
+ self.playerItem = AVPlayerItem(asset: composition)
146
+ self.playerItem?.textStyleRules = textStyle
147
+
148
+ } catch {
149
+ self.playerItem = AVPlayerItem(asset: self.videoAsset)
150
+ }
151
+ } else {
152
+ self.playerItem = AVPlayerItem(asset: self.videoAsset)
153
+ }
154
+ } catch {
155
+ self.playerItem = AVPlayerItem(asset: self.videoAsset)
156
+ }
157
+ } else {
158
+ self.playerItem = AVPlayerItem(asset: self.videoAsset)
159
+ }
160
+ } else {
161
+ self.playerItem = AVPlayerItem(asset: self.videoAsset)
162
+ }
163
+ } else {
164
+ self.playerItem = AVPlayerItem(asset: self.videoAsset)
165
+ }
166
+ self.player = AVPlayer(playerItem: self.playerItem)
167
+ self.player?.currentItem?.audioTimePitchAlgorithm = .timeDomain
168
+ if !self._showControls {
169
+ self.videoPlayer.showsPlaybackControls = false
170
+ }
171
+ self.videoPlayer.player = self.player
172
+ self.videoPlayer.updatesNowPlayingInfoCenter = false
173
+ if #available(iOS 13.0, *) {
174
+ self.videoPlayer.isModalInPresentation = true
175
+ } else {
176
+ // Fallback on earlier versions
177
+ }
178
+ self.videoPlayer.allowsPictureInPicturePlayback = false
179
+ if isPIPModeAvailable && self._pipEnabled {
180
+ self.videoPlayer.allowsPictureInPicturePlayback = true
181
+ }
182
+
183
+ self._isLoaded.updateValue(false, forKey: self._videoId)
184
+
185
+ }
186
+ // swiftlint:enable cyclomatic_complexity
187
+ // swiftlint:enable function_body_length
188
+
189
+ private func setSubTitleStyle(options: [String: Any]) -> [AVTextStyleRule] {
190
+ var styles: [AVTextStyleRule] = []
191
+ var backColor: [Float] = [1.0, 0.0, 0.0, 0.0]
192
+ if let bckCol = options["backgroundColor"] as? String {
193
+ let color = self.getColorFromRGBA(rgba: bckCol)
194
+ backColor = color.count > 0 ? color : backColor
195
+ }
196
+ if let textStyle: AVTextStyleRule = AVTextStyleRule(textMarkupAttributes: [
197
+ kCMTextMarkupAttribute_CharacterBackgroundColorARGB as String:
198
+ backColor
199
+ ]) {
200
+ styles.append(textStyle)
201
+ }
202
+
203
+ var foreColor: [Float] = [1.0, 1.0, 1.0, 1.0]
204
+ if let foreCol = options["foregroundColor"] as? String {
205
+ let color = self.getColorFromRGBA(rgba: foreCol)
206
+ foreColor = color.count > 0 ? color : foreColor
207
+ }
208
+ if let textStyle1: AVTextStyleRule = AVTextStyleRule(textMarkupAttributes: [
209
+ kCMTextMarkupAttribute_ForegroundColorARGB as String: foreColor
210
+ ]) {
211
+ styles.append(textStyle1)
212
+ }
213
+ var ftSize = 160
214
+ if let pixSize = options["fontSize"] as? Int {
215
+ ftSize = pixSize * 10
216
+ }
217
+ if let textStyle2: AVTextStyleRule = AVTextStyleRule(textMarkupAttributes: [
218
+ kCMTextMarkupAttribute_RelativeFontSize as String: ftSize,
219
+ kCMTextMarkupAttribute_CharacterEdgeStyle as String: kCMTextMarkupCharacterEdgeStyle_None
220
+ ]) {
221
+ styles.append(textStyle2)
222
+ }
223
+ return styles
224
+ }
225
+ // MARK: - Add Observers
226
+
227
+ // swiftlint:disable function_body_length
228
+ // swiftlint:disable cyclomatic_complexity
229
+ private func addObservers() {
230
+
231
+ self.itemStatusObserver = self.playerItem?
232
+ .observe(\.status, options: [.new, .old],
233
+ changeHandler: {(playerItem, _) in
234
+ // Switch over the status
235
+ switch playerItem.status {
236
+ case .readyToPlay:
237
+ // Player item is ready to play.
238
+ if self._firstReadyToPlay {
239
+ self._isLoaded.updateValue(true, forKey: self._videoId)
240
+ self._isReadyToPlay = true
241
+ isVideoEnded = false
242
+ if let item = self.playerItem {
243
+ self._currentTime = CMTimeGetSeconds(item.currentTime())
244
+ }
245
+ let vId: [String: Any] = ["fromPlayerId": self._videoId, "currentTime": self._currentTime,
246
+ "videoRate": self._videoRate]
247
+ NotificationCenter.default.post(name: .playerItemReady, object: nil, userInfo: vId)
248
+ self._firstReadyToPlay = false
249
+
250
+ self.setNowPlayingInfo()
251
+ self.setRemoteCommandCenter()
252
+ self.setNowPlayingImage()
253
+ }
254
+ case .failed:
255
+ print("failing to load")
256
+ self._isLoaded.updateValue(false, forKey: self._videoId)
257
+ case .unknown:
258
+ // Player item is not yet ready.
259
+ print("playerItem not yet ready")
260
+
261
+ @unknown default:
262
+ print("playerItem Error \(String(describing: self.playerItem?.error))")
263
+ }
264
+
265
+ })
266
+
267
+ self.itemBufferObserver = self.playerItem?
268
+ .observe(\.isPlaybackBufferEmpty,
269
+ options: [.new, .old], changeHandler: {(playerItem, _) in
270
+ let empty: Bool = ((self.playerItem?.isPlaybackBufferEmpty) != nil)
271
+ if empty {
272
+ self._isBufferEmpty.updateValue(true, forKey: self._videoId)
273
+ } else {
274
+ self._isBufferEmpty.updateValue(false, forKey: self._videoId)
275
+ }
276
+ })
277
+ self.playerRateObserver = self.player?
278
+ .observe(\.rate, options: [.new, .old], changeHandler: {(player, _) in
279
+ let rate: Float = player.rate
280
+ if let item = self.playerItem {
281
+ self._currentTime = CMTimeGetSeconds(item.currentTime())
282
+ self._duration = CMTimeGetSeconds(item.duration)
283
+ }
284
+ let vId: [String: Any] = [
285
+ "fromPlayerId": self._videoId,
286
+ "currentTime": self._currentTime,
287
+ "videoRate": self._videoRate
288
+ ]
289
+
290
+ if !(self._isLoaded[self._videoId] ?? true) {
291
+ print("AVPlayer Rate for player \(self._videoId): Loading")
292
+ } else if rate > 0 && self._isReadyToPlay {
293
+ if rate != self._videoRate {
294
+ player.rate = self._videoRate
295
+ }
296
+
297
+ self.isPlaying = true
298
+ NotificationCenter.default.post(name: .playerItemPlay, object: nil, userInfo: vId)
299
+ } else if rate == 0 && !isVideoEnded && abs(self._currentTime - self._duration) < 0.2 {
300
+ self.isPlaying = false
301
+ player.seek(to: CMTime.zero)
302
+ self._currentTime = 0
303
+ if /*!isInPIPMode && */self._exitOnEnd {
304
+ isVideoEnded = true
305
+ NotificationCenter.default.post(name: .playerItemEnd, object: nil, userInfo: vId)
306
+ } else {
307
+ if self._loopOnEnd {
308
+ self.play()
309
+ }
310
+ }
311
+ } else if rate == 0 {
312
+ if !isInPIPMode && !isInBackgroundMode && !isRateZero {
313
+ self.isPlaying = false
314
+ if !self.videoPlayer.isBeingDismissed {
315
+ print("AVPlayer Rate for player \(self._videoId): Paused")
316
+ NotificationCenter.default.post(name: .playerItemPause, object: nil, userInfo: vId)
317
+ }
318
+ } else {
319
+ isRateZero = true
320
+ }
321
+ } else if self._isBufferEmpty[self._videoId] ?? true {
322
+ print("AVPlayer Rate for player \(self._videoId): Buffer Empty Loading")
323
+ }
324
+ })
325
+ self.videoPlayerFrameObserver = self.videoPlayer
326
+ .observe(\.view.frame, options: [.new, .old],
327
+ changeHandler: {(_, _) in
328
+ if !isInPIPMode {
329
+ if self.videoPlayer.isBeingDismissed && !isVideoEnded {
330
+ NotificationCenter.default.post(name: .playerFullscreenDismiss, object: nil)
331
+ }
332
+ }
333
+
334
+ })
335
+ self.videoPlayerMoveObserver = self.videoPlayer
336
+ .observe(\.view.center, options: [.new, .old],
337
+ changeHandler: {(_, _) in
338
+ if !isInPIPMode {
339
+ if self.videoPlayer.isBeingDismissed && !isVideoEnded {
340
+ NotificationCenter.default.post(name: .playerFullscreenDismiss, object: nil)
341
+ }
342
+
343
+ }
344
+
345
+ })
346
+ }
347
+
348
+ // swiftlint:enable function_body_length
349
+ // swiftlint:enable cyclomatic_complexity
350
+
351
+ // MARK: - Remove Observers
352
+
353
+ func removeObservers() {
354
+ self.itemStatusObserver?.invalidate()
355
+ self.itemBufferObserver?.invalidate()
356
+ self.playerRateObserver?.invalidate()
357
+ self.videoPlayerFrameObserver?.invalidate()
358
+ }
359
+
360
+ // MARK: - Required init
361
+
362
+ public required init?(coder: NSCoder) {
363
+ fatalError("init(coder:) has not been implemented")
364
+ }
365
+ // MARK: - Set-up Public functions
366
+
367
+ @objc func play() {
368
+ self.isPlaying = true
369
+ self.player?.play()
370
+ self.player?.rate = _videoRate
371
+ }
372
+ @objc func pause() {
373
+ self.isPlaying = false
374
+ self.player?.pause()
375
+ }
376
+ @objc func didFinishPlaying() -> Bool {
377
+ return isVideoEnded
378
+ }
379
+ @objc func getDuration() -> Double {
380
+ return Double(CMTimeGetSeconds(self.videoAsset.duration))
381
+ }
382
+ @objc func getCurrentTime() -> Double {
383
+ return self._currentTime
384
+ }
385
+ // This func will return the updated currentTime of player item
386
+ // getCurrentTime() is only updated when player plays, pauses, seek, etc
387
+ // the function is only used in playerFullscreenDismiss() Notification
388
+ public func getRealCurrentTime() -> Double {
389
+ if let item = self.playerItem {
390
+ let currentTime = CMTimeGetSeconds(item.currentTime())
391
+ return currentTime
392
+ } else {
393
+ return 0
394
+ }
395
+ }
396
+ @objc func setCurrentTime(time: Double) {
397
+ let seekTime: CMTime = CMTimeMake(value: Int64(time*1000), timescale: 1000)
398
+ self.player?.seek(to: seekTime)
399
+ self._currentTime = time
400
+ }
401
+ @objc func getVolume() -> Float {
402
+ if let player = self.player {
403
+ return player.volume
404
+ } else {
405
+ return 1.0
406
+ }
407
+ }
408
+ @objc func setVolume(volume: Float) {
409
+ self.player?.volume = volume
410
+ }
411
+ @objc func getRate() -> Float {
412
+ return _videoRate
413
+ }
414
+
415
+ @objc func setRate(rate: Float) {
416
+ _videoRate = rate
417
+ }
418
+ @objc func getMuted() -> Bool {
419
+ return ((self.player?.isMuted) != nil)
420
+ }
421
+ @objc func setMuted(muted: Bool) {
422
+ self.player?.isMuted = muted
423
+ }
424
+
425
+ private func getColorFromRGBA(rgba: String) -> [Float] {
426
+ if let oPar = rgba.firstIndex(of: "(") {
427
+ if let cPar = rgba.firstIndex(of: ")") {
428
+ let strColor = rgba[rgba.index(after: oPar)..<cPar]
429
+ let array = strColor.components(separatedBy: ",")
430
+ if array.count == 4 {
431
+ var retArray: [Float] = []
432
+ retArray.append((array[3]
433
+ .trimmingCharacters(in: .whitespaces) as NSString)
434
+ .floatValue)
435
+ retArray.append((array[0]
436
+ .trimmingCharacters(in: .whitespaces) as NSString)
437
+ .floatValue / 255)
438
+ retArray.append((array[1]
439
+ .trimmingCharacters(in: .whitespaces) as NSString)
440
+ .floatValue / 255)
441
+ retArray.append((array[2]
442
+ .trimmingCharacters(in: .whitespaces) as NSString)
443
+ .floatValue / 255)
444
+ return retArray
445
+ } else {
446
+ return []
447
+ }
448
+ } else {
449
+ return []
450
+ }
451
+ } else {
452
+ return []
453
+ }
454
+ }
455
+
456
+ private func srtSubtitleToVtt(srtURL: URL) -> URL {
457
+ guard let cachesURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
458
+ fatalError("Couldn't get caches directory")
459
+ }
460
+ let vttFileName = UUID().uuidString + ".vtt"
461
+ let vttURL = cachesURL.appendingPathComponent(vttFileName)
462
+ let session = URLSession(configuration: .default)
463
+ let vttFolderURL = vttURL.deletingLastPathComponent()
464
+ do {
465
+ try FileManager.default.createDirectory(at: vttFolderURL, withIntermediateDirectories: true, attributes: nil)
466
+ } catch let error {
467
+ print("Creating folder error: ", error)
468
+ }
469
+ let task = session.dataTask(with: srtURL) { (data, _, error) in
470
+ guard let data = data, error == nil else {
471
+ print("Download failed: \(error?.localizedDescription ?? "ukn")")
472
+ return
473
+ }
474
+ do {
475
+ let tempSRTURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("subtitulos.srt")
476
+ try data.write(to: tempSRTURL)
477
+ let srtContent = try String(contentsOf: tempSRTURL, encoding: .utf8)
478
+ let vttContent = srtContent.replacingOccurrences(of: ",", with: ".")
479
+ let vttString = "WEBVTT\n\n" + vttContent
480
+ try vttString.write(toFile: vttURL.path, atomically: true, encoding: .utf8)
481
+ try FileManager.default.removeItem(at: tempSRTURL)
482
+
483
+ } catch let error {
484
+ print("Processing subs error: \(error)")
485
+ exit(1)
486
+ }
487
+
488
+ }
489
+
490
+ task.resume()
491
+ return vttURL
492
+ }
493
+
494
+ func setRemoteCommandCenter() {
495
+ let rcc = MPRemoteCommandCenter.shared()
496
+
497
+ rcc.playCommand.isEnabled = true
498
+ rcc.playCommand.addTarget {event in
499
+ self.play()
500
+ return .success
501
+ }
502
+ rcc.pauseCommand.isEnabled = true
503
+ rcc.pauseCommand.addTarget {event in
504
+ self.pause()
505
+ return .success
506
+ }
507
+ rcc.changePlaybackPositionCommand.isEnabled = true
508
+ rcc.changePlaybackPositionCommand.addTarget {event in
509
+ let seconds = (event as? MPChangePlaybackPositionCommandEvent)?.positionTime ?? 0
510
+ let time = CMTime(seconds: seconds, preferredTimescale: 1)
511
+ self.player?.seek(to: time)
512
+ return .success
513
+ }
514
+ rcc.skipForwardCommand.isEnabled = true
515
+ rcc.skipForwardCommand.addTarget {event in
516
+ if let player = self.player, let currentItem = player.currentItem {
517
+ let currentTime = CMTimeGetSeconds(currentItem.currentTime()) + 10
518
+ self.player?.seek(to: CMTimeMakeWithSeconds(currentTime, preferredTimescale: 1))
519
+ return .success
520
+ } else {
521
+ return .commandFailed
522
+ }
523
+ }
524
+ rcc.skipBackwardCommand.isEnabled = true
525
+ rcc.skipBackwardCommand.addTarget {event in
526
+ if let player = self.player, let currentItem = player.currentItem {
527
+ let currentTime = CMTimeGetSeconds(currentItem.currentTime()) - 10
528
+ self.player?.seek(to: CMTimeMakeWithSeconds(currentTime, preferredTimescale: 1))
529
+ return .success
530
+ } else {
531
+ return .commandFailed
532
+ }
533
+ }
534
+
535
+ // Next and previous track buttons are disabled because we don't have more than 1 video
536
+ rcc.nextTrackCommand.isEnabled = false
537
+ rcc.previousTrackCommand.isEnabled = false
538
+ }
539
+
540
+ func setNowPlayingImage() {
541
+ if let artwork = self._artwork {
542
+ let session = URLSession(configuration: .default)
543
+ let image = URL(string: artwork)!
544
+ let task = session.dataTask(with: image) { (data, response, error) in
545
+ guard let imageData = data, error == nil else {
546
+ print("Error while downloading the image: \(error?.localizedDescription ?? "")")
547
+ return
548
+ }
549
+
550
+ let image = UIImage(data: imageData)
551
+ DispatchQueue.main.async {
552
+ var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
553
+ nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image?.size ?? CGSize.zero, requestHandler: { _ in
554
+ return image ?? UIImage()
555
+ })
556
+ MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
557
+ }
558
+ }
559
+ task.resume()
560
+ }
561
+ }
562
+
563
+ func setNowPlayingInfo() {
564
+ var nowPlayingInfo = [String: Any]()
565
+
566
+ if let title = self._title {
567
+ nowPlayingInfo[MPMediaItemPropertyTitle] = title
568
+ }
569
+ if let smalltitle = self._smallTitle {
570
+ nowPlayingInfo[MPMediaItemPropertyArtist] = smalltitle
571
+ }
572
+
573
+ nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = NSNumber(value: MPNowPlayingInfoMediaType.video.rawValue)
574
+ MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
575
+ UIApplication.shared.beginReceivingRemoteControlEvents()
576
+ periodicTimeObserver = self.player?.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 1), queue: DispatchQueue.main) { [weak self] time in
577
+ guard let self = self else { return }
578
+
579
+ var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
580
+ if let currentItem = self.player?.currentItem,
581
+ let currentTime = self.player?.currentTime(),
582
+ currentItem.status == .readyToPlay {
583
+
584
+ let elapsedTime = CMTimeGetSeconds(currentTime)
585
+ if currentItem.isPlaybackLikelyToKeepUp {
586
+ nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = self.player?.rate
587
+ } else {
588
+ nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = 0
589
+ }
590
+
591
+ nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = Float(elapsedTime)
592
+ nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = currentItem.duration.seconds
593
+
594
+ MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
595
+ }
596
+ }
597
+ }
598
+ }
599
+
600
+ // swiftlint:enable type_body_length
601
+ // swiftlint:enable file_length