@capgo/capacitor-video-player 8.2.15 → 8.3.1
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/README.md +36 -5
- package/android/src/main/java/com/capgo/videoplayer/FullscreenExoPlayerFragment.java +145 -53
- package/android/src/main/java/com/capgo/videoplayer/SubtitleOptionOverrides.java +84 -0
- package/android/src/main/java/com/capgo/videoplayer/VideoChapter.java +14 -0
- package/android/src/main/java/com/capgo/videoplayer/VideoPlayer.java +3 -0
- package/android/src/main/java/com/capgo/videoplayer/VideoPlayerPlugin.java +48 -2
- package/android/src/main/res/drawable/ic_chapters_white.xml +10 -0
- package/android/src/main/res/layout/exo_playback_control_view.xml +9 -0
- package/android/src/main/res/values/strings.xml +2 -0
- package/dist/docs.json +43 -4
- package/dist/esm/definitions.d.ts +53 -3
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/VideoPlayerPlugin/AVPlayerControlsVisibilityObserver.swift +13 -0
- package/ios/Sources/VideoPlayerPlugin/FullscreenVideoPlayer.swift +215 -0
- package/ios/Sources/VideoPlayerPlugin/SubtitleStyleOptions.swift +7 -0
- package/ios/Sources/VideoPlayerPlugin/VideoChapter.swift +45 -0
- package/ios/Sources/VideoPlayerPlugin/VideoPlayerCastController.swift +23 -5
- package/ios/Sources/VideoPlayerPlugin/VideoPlayerPlugin.swift +47 -1
- package/ios/Tests/VideoPlayerPluginTests/VideoChapterTests.swift +30 -0
- package/package.json +6 -4
|
@@ -29,7 +29,12 @@ final class VideoPlayerCastController: NSObject {
|
|
|
29
29
|
private weak var observedRemoteMediaClient: GCKRemoteMediaClient?
|
|
30
30
|
private var mediaLoadRequest: GCKRequest?
|
|
31
31
|
private var pendingCastCommands: [PendingCastCommand] = []
|
|
32
|
-
private var isLoadedOnCast = false
|
|
32
|
+
private var isLoadedOnCast = false {
|
|
33
|
+
didSet {
|
|
34
|
+
guard oldValue != isLoadedOnCast else { return }
|
|
35
|
+
onCastingStateChange?()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
33
38
|
private var isLoadingOnCast = false
|
|
34
39
|
private var localWasPlaying = false
|
|
35
40
|
private var isDetached = false
|
|
@@ -38,6 +43,7 @@ final class VideoPlayerCastController: NSObject {
|
|
|
38
43
|
private var onPlay: (() -> Void)?
|
|
39
44
|
private var onPause: (() -> Void)?
|
|
40
45
|
private var onEnd: (() -> Void)?
|
|
46
|
+
private var onCastingStateChange: (() -> Void)?
|
|
41
47
|
private let controlsVisibilityObserver = AVPlayerControlsVisibilityObserver()
|
|
42
48
|
private var localSeekTimeObserver: Any?
|
|
43
49
|
private var timeJumpObserver: NSObjectProtocol?
|
|
@@ -275,6 +281,10 @@ final class VideoPlayerCastController: NSObject {
|
|
|
275
281
|
func setOnEnd(_ callback: @escaping () -> Void) {
|
|
276
282
|
onEnd = callback
|
|
277
283
|
}
|
|
284
|
+
|
|
285
|
+
func setOnCastingStateChange(_ callback: @escaping () -> Void) {
|
|
286
|
+
onCastingStateChange = callback
|
|
287
|
+
}
|
|
278
288
|
}
|
|
279
289
|
|
|
280
290
|
private extension VideoPlayerCastController {
|
|
@@ -364,13 +374,17 @@ private extension VideoPlayerCastController {
|
|
|
364
374
|
button.accessibilityLabel = "Cast"
|
|
365
375
|
|
|
366
376
|
overlayView.addSubview(button)
|
|
377
|
+
let layout = VideoPlayerCastOverlayLayout.self
|
|
367
378
|
NSLayoutConstraint.activate([
|
|
368
|
-
button.widthAnchor.constraint(equalToConstant:
|
|
369
|
-
button.heightAnchor.constraint(equalToConstant:
|
|
379
|
+
button.widthAnchor.constraint(equalToConstant: layout.controlSize),
|
|
380
|
+
button.heightAnchor.constraint(equalToConstant: layout.controlSize),
|
|
370
381
|
// Position below AVPlayerViewController's top row of controls (Done/X and route picker)
|
|
371
382
|
// using trailing to stay away from the leading-side dismiss button.
|
|
372
|
-
button.topAnchor.constraint(equalTo: overlayView.safeAreaLayoutGuide.topAnchor, constant:
|
|
373
|
-
button.trailingAnchor.constraint(
|
|
383
|
+
button.topAnchor.constraint(equalTo: overlayView.safeAreaLayoutGuide.topAnchor, constant: layout.topRowOffset),
|
|
384
|
+
button.trailingAnchor.constraint(
|
|
385
|
+
equalTo: overlayView.safeAreaLayoutGuide.trailingAnchor,
|
|
386
|
+
constant: -layout.trailingInset
|
|
387
|
+
)
|
|
374
388
|
])
|
|
375
389
|
|
|
376
390
|
castButton = button
|
|
@@ -930,6 +944,10 @@ final class VideoPlayerCastController {
|
|
|
930
944
|
func setOnEnd(_ callback: @escaping () -> Void) {
|
|
931
945
|
_ = callback
|
|
932
946
|
}
|
|
947
|
+
|
|
948
|
+
func setOnCastingStateChange(_ callback: @escaping () -> Void) {
|
|
949
|
+
_ = callback
|
|
950
|
+
}
|
|
933
951
|
}
|
|
934
952
|
|
|
935
953
|
#endif
|
|
@@ -8,7 +8,7 @@ import AVKit
|
|
|
8
8
|
*/
|
|
9
9
|
@objc(VideoPlayerPlugin)
|
|
10
10
|
public class VideoPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
11
|
-
private let pluginVersion: String = "8.
|
|
11
|
+
private let pluginVersion: String = "8.3.1"
|
|
12
12
|
public let identifier = "VideoPlayerPlugin"
|
|
13
13
|
public let jsName = "VideoPlayer"
|
|
14
14
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -85,6 +85,8 @@ extension VideoPlayerPlugin {
|
|
|
85
85
|
let chromecastUrl = call.getString("chromecastUrl")
|
|
86
86
|
let audioCategory = call.getString("audioCategory")
|
|
87
87
|
let subtitleTracks = Self.parseSubtitleTracks(from: call)
|
|
88
|
+
let subtitleStyleOptions = Self.parseSubtitleStyleOptions(from: call)
|
|
89
|
+
let chapters = Self.parseChapters(from: call)
|
|
88
90
|
let drmOptions = Self.parseDrmOptions(from: call)
|
|
89
91
|
|
|
90
92
|
// Create video player
|
|
@@ -103,6 +105,8 @@ extension VideoPlayerPlugin {
|
|
|
103
105
|
smallTitle: smallTitle,
|
|
104
106
|
artwork: artwork,
|
|
105
107
|
subtitleTracks: subtitleTracks,
|
|
108
|
+
subtitleStyleOptions: subtitleStyleOptions,
|
|
109
|
+
chapters: chapters,
|
|
106
110
|
fairplayCertificateUrl: drmOptions.fairplayCertificateUrl,
|
|
107
111
|
fairplayContentKeySpcUrl: drmOptions.fairplayContentKeySpcUrl,
|
|
108
112
|
fairplayAssetId: drmOptions.fairplayAssetId,
|
|
@@ -139,6 +143,48 @@ extension VideoPlayerPlugin {
|
|
|
139
143
|
}
|
|
140
144
|
}
|
|
141
145
|
|
|
146
|
+
private static func parseSubtitleStyleOptions(from call: CAPPluginCall) -> SubtitleStyleOptions? {
|
|
147
|
+
guard let options = call.getObject("subtitleOptions") else {
|
|
148
|
+
return nil
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let fontSize: Double?
|
|
152
|
+
if let number = options["fontSize"] as? NSNumber {
|
|
153
|
+
fontSize = number.doubleValue
|
|
154
|
+
} else {
|
|
155
|
+
fontSize = nil
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return SubtitleStyleOptions(
|
|
159
|
+
foregroundColor: options["foregroundColor"] as? String,
|
|
160
|
+
backgroundColor: options["backgroundColor"] as? String,
|
|
161
|
+
fontSize: fontSize
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private static func parseChapters(from call: CAPPluginCall) -> [VideoChapter] {
|
|
166
|
+
guard let entries = call.getArray("chapters") else {
|
|
167
|
+
return []
|
|
168
|
+
}
|
|
169
|
+
var parsed: [VideoChapter] = []
|
|
170
|
+
for (index, entry) in entries.enumerated() {
|
|
171
|
+
guard let jsObject = entry as? JSObject else {
|
|
172
|
+
print("VideoPlayerPlugin: skipping invalid chapter at index \(index)")
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
var dictionary: [String: Any] = [:]
|
|
176
|
+
for (key, value) in jsObject {
|
|
177
|
+
dictionary[key] = value
|
|
178
|
+
}
|
|
179
|
+
if let chapter = VideoChapter(dictionary: dictionary) {
|
|
180
|
+
parsed.append(chapter)
|
|
181
|
+
} else {
|
|
182
|
+
print("VideoPlayerPlugin: skipping invalid chapter at index \(index)")
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return parsed.sorted { $0.startTime < $1.startTime }
|
|
186
|
+
}
|
|
187
|
+
|
|
142
188
|
private static func parseSubtitleTracks(from call: CAPPluginCall) -> [VideoSubtitleTrack] {
|
|
143
189
|
let subtitlesArray = call.getArray("subtitles", JSObject.self)?.map { entry -> [String: Any] in
|
|
144
190
|
var dict: [String: Any] = [:]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import VideoPlayerPlugin
|
|
3
|
+
|
|
4
|
+
class VideoChapterTests: XCTestCase {
|
|
5
|
+
func testRejectsBooleanStartTime() {
|
|
6
|
+
XCTAssertNil(VideoChapter(dictionary: [
|
|
7
|
+
"title": "Intro",
|
|
8
|
+
"startTime": NSNumber(value: true)
|
|
9
|
+
]))
|
|
10
|
+
XCTAssertNil(VideoChapter(dictionary: [
|
|
11
|
+
"title": "Intro",
|
|
12
|
+
"startTime": NSNumber(value: false)
|
|
13
|
+
]))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func testAcceptsNumericStartTime() {
|
|
17
|
+
let chapter = VideoChapter(dictionary: [
|
|
18
|
+
"title": "Intro",
|
|
19
|
+
"startTime": NSNumber(value: 12.5)
|
|
20
|
+
])
|
|
21
|
+
XCTAssertEqual(chapter?.startTime, 12.5)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func testRejectsStartTimeAtOrAboveIntMaxDouble() {
|
|
25
|
+
XCTAssertNil(VideoChapter(dictionary: [
|
|
26
|
+
"title": "Edge",
|
|
27
|
+
"startTime": Double(Int.max)
|
|
28
|
+
]))
|
|
29
|
+
}
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-video-player",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.3.1",
|
|
4
4
|
"description": "Capacitor plugin to play video in native player",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
"check:cap9-deprecated": "node scripts/check-cap9-deprecated.mjs",
|
|
54
54
|
"example:install": "cd example-app && bun install --frozen-lockfile",
|
|
55
55
|
"example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build",
|
|
56
|
-
"changelog:ai": "node scripts/generate-ai-changelog.mjs"
|
|
56
|
+
"changelog:ai": "node scripts/generate-ai-changelog.mjs",
|
|
57
|
+
"screenshots:pr-108": "node docs/pr-108-screenshots/capture.mjs"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
60
|
"@capacitor/android": "^8.0.0",
|
|
@@ -69,13 +70,14 @@
|
|
|
69
70
|
"eslint": "^10.0.0",
|
|
70
71
|
"eslint-plugin-import": "^2.31.0",
|
|
71
72
|
"hls.js": "^1.5.18",
|
|
73
|
+
"playwright": "1.65.0",
|
|
72
74
|
"prettier": "^3.6.2",
|
|
73
75
|
"prettier-plugin-java": "^2.7.7",
|
|
76
|
+
"prettier-pretty-check": "^0.2.0",
|
|
74
77
|
"rimraf": "^6.1.0",
|
|
75
78
|
"rollup": "^4.53.2",
|
|
76
79
|
"swiftlint": "^2.0.0",
|
|
77
|
-
"typescript": "^6.0.3"
|
|
78
|
-
"prettier-pretty-check": "^0.2.0"
|
|
80
|
+
"typescript": "^6.0.3"
|
|
79
81
|
},
|
|
80
82
|
"peerDependencies": {
|
|
81
83
|
"@capacitor/core": ">=8.0.0",
|