@picsa/capacitor-video-player 7.1.0
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.
- package/CapacitorVideoPlayer.podspec +17 -0
- package/LICENSE +21 -0
- package/android/build.gradle +71 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/CapacitorVideoPlayer.java +71 -0
- package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/CapacitorVideoPlayerPlugin.java +1256 -0
- package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/FullscreenExoPlayerFragment.java +1595 -0
- package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/Notifications/MyRunnable.java +21 -0
- package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/Notifications/NotificationCenter.java +61 -0
- package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/PickerVideo/AdapterVideoList.java +47 -0
- package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/PickerVideo/ModelVideo.java +49 -0
- package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/PickerVideo/PickerVideoFragment.java +119 -0
- package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/PickerVideo/VideoRecyclerViewHolder.java +65 -0
- package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/Utilities/FilesUtils.java +101 -0
- package/android/src/main/java/com/jeep/plugin/capacitor/capacitorvideoplayer/Utilities/FragmentUtils.java +32 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/android/src/main/res/drawable/bg_round_rect_white_50.xml +9 -0
- package/android/src/main/res/drawable/bg_rounded_rectangle_white_corner_rounded.xml +10 -0
- package/android/src/main/res/drawable/exo_close_btn.xml +12 -0
- package/android/src/main/res/drawable/gradient_transparent_middle.xml +12 -0
- package/android/src/main/res/drawable/ic_arrow_left.xml +5 -0
- package/android/src/main/res/drawable/ic_baseline_lq.xml +7 -0
- package/android/src/main/res/drawable/ic_exo_icon_fastforward.xml +35 -0
- package/android/src/main/res/drawable/ic_exo_icon_pause.xml +26 -0
- package/android/src/main/res/drawable/ic_exo_icon_play.xml +36 -0
- package/android/src/main/res/drawable/ic_exo_icon_rewind.xml +35 -0
- package/android/src/main/res/drawable/ic_expand.xml +5 -0
- package/android/src/main/res/drawable/ic_fit.xml +5 -0
- package/android/src/main/res/drawable/ic_image_background.xml +12 -0
- package/android/src/main/res/drawable/ic_img_16_9_background.xml +10 -0
- package/android/src/main/res/drawable/ic_img_9_16_background.xml +10 -0
- package/android/src/main/res/drawable/ic_outline_lock.xml +5 -0
- package/android/src/main/res/drawable/ic_outline_lock_open.xml +5 -0
- package/android/src/main/res/drawable/ic_pip_white.xml +5 -0
- package/android/src/main/res/drawable/ic_views.xml +18 -0
- package/android/src/main/res/drawable/ic_zoom.xml +5 -0
- package/android/src/main/res/layout/bridge_layout_main.xml +15 -0
- package/android/src/main/res/layout/exo_playback_control_view.xml +289 -0
- package/android/src/main/res/layout/exoplayer_layout_youtube.xml +361 -0
- package/android/src/main/res/layout/fragment_fs_exoplayer.xml +50 -0
- package/android/src/main/res/layout/fragment_picker_video.xml +21 -0
- package/android/src/main/res/layout/row_video.xml +76 -0
- package/android/src/main/res/values/colors.xml +14 -0
- package/android/src/main/res/values/strings.xml +3 -0
- package/android/src/main/res/values/styles.xml +3 -0
- package/dist/docs.json +831 -0
- package/dist/esm/definitions.d.ts +322 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web-utils/video-types.d.ts +16 -0
- package/dist/esm/web-utils/video-types.js +15 -0
- package/dist/esm/web-utils/video-types.js.map +1 -0
- package/dist/esm/web-utils/videoplayer.d.ts +30 -0
- package/dist/esm/web-utils/videoplayer.js +315 -0
- package/dist/esm/web-utils/videoplayer.js.map +1 -0
- package/dist/esm/web.d.ts +123 -0
- package/dist/esm/web.js +675 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +1017 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +1019 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/CapacitorVideoPlayer.swift +64 -0
- package/ios/Plugin/CapacitorVideoPlayerPlugin.h +10 -0
- package/ios/Plugin/CapacitorVideoPlayerPlugin.m +22 -0
- package/ios/Plugin/CapacitorVideoPlayerPlugin.swift +632 -0
- package/ios/Plugin/Extensions/CapacitorVideoPlayerPlugin.AVPlayerViewController.swift +59 -0
- package/ios/Plugin/Extensions/CapacitorVideoPlayerPlugin.File.swift +126 -0
- package/ios/Plugin/Extensions/CapacitorVideoPlayerPlugin.Fullscreen.swift +94 -0
- package/ios/Plugin/Extensions/CapacitorVideoPlayerPlugin.Notifications.swift +140 -0
- package/ios/Plugin/Extensions/CapacitorVideoPlayerPlugin.Observers.swift +180 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/VideoPicker/VideoHelper.swift +127 -0
- package/ios/Plugin/VideoPicker/VideoPickerViewController.swift +144 -0
- package/ios/Plugin/VideoPlayer/Extensions/AVURLAsset.swift +18 -0
- package/ios/Plugin/VideoPlayer/Extensions/AllOrientationAVPlayerController.swift +12 -0
- package/ios/Plugin/VideoPlayer/Extensions/CMTime.swift +23 -0
- package/ios/Plugin/VideoPlayer/Extensions/LandscapeAVPlayerController.swift +12 -0
- package/ios/Plugin/VideoPlayer/Extensions/Notification.Name.swift +18 -0
- package/ios/Plugin/VideoPlayer/Extensions/PortraitAVPlayerController.swift +12 -0
- package/ios/Plugin/VideoPlayer/Extensions/View.swift +54 -0
- package/ios/Plugin/VideoPlayer/FullScreenVideoPlayerView.swift +601 -0
- package/ios/Plugin/VideoPlayer/Models/Video.swift +28 -0
- package/ios/Plugin/VideoPlayer/Models/VideoModel.swift +42 -0
- package/package.json +92 -0
- package/readme.md +190 -0
|
@@ -0,0 +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
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Video.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 Foundation
|
|
10
|
+
class Video: NSObject {
|
|
11
|
+
var urlPath: String?
|
|
12
|
+
var imgPath: String?
|
|
13
|
+
var title: String?
|
|
14
|
+
var content: String?
|
|
15
|
+
var width: Int?
|
|
16
|
+
var height: Int?
|
|
17
|
+
override init() {
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
init(urlPath: String, imgPath: String?, title: String, content: String, width: Int, height: Int) {
|
|
21
|
+
self.urlPath = urlPath
|
|
22
|
+
self.imgPath = imgPath
|
|
23
|
+
self.title = title
|
|
24
|
+
self.content = content
|
|
25
|
+
self.width = width
|
|
26
|
+
self.height = height
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//
|
|
2
|
+
// VideoModel.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
|
+
|
|
11
|
+
class VideoModel: NSObject {
|
|
12
|
+
var videos: [Video] = []
|
|
13
|
+
|
|
14
|
+
func addVideo(video: Video) {
|
|
15
|
+
videos.append(video)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func getVideos() -> [Video] {
|
|
19
|
+
return videos
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func printVideos() {
|
|
23
|
+
for video in self.videos {
|
|
24
|
+
guard let urlPath: String = video.urlPath else {
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
guard let title: String = video.title else {
|
|
28
|
+
continue
|
|
29
|
+
}
|
|
30
|
+
guard let content: String = video.content else {
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
guard let width: Int = video.width else {
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
guard let height: Int = video.height else {
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
print("Video \(urlPath) \n \(title) \n \(content) \n \(width) \(height)")
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|