@capgo/capacitor-video-player 8.2.14 → 8.3.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.
Files changed (22) hide show
  1. package/README.md +68 -33
  2. package/android/src/main/java/com/capgo/videoplayer/FullscreenExoPlayerFragment.java +145 -53
  3. package/android/src/main/java/com/capgo/videoplayer/SubtitleOptionOverrides.java +84 -0
  4. package/android/src/main/java/com/capgo/videoplayer/VideoChapter.java +14 -0
  5. package/android/src/main/java/com/capgo/videoplayer/VideoPlayer.java +3 -0
  6. package/android/src/main/java/com/capgo/videoplayer/VideoPlayerPlugin.java +48 -2
  7. package/android/src/main/res/drawable/ic_chapters_white.xml +10 -0
  8. package/android/src/main/res/layout/exo_playback_control_view.xml +9 -0
  9. package/android/src/main/res/values/strings.xml +2 -0
  10. package/dist/docs.json +44 -5
  11. package/dist/esm/definitions.d.ts +58 -6
  12. package/dist/esm/definitions.js.map +1 -1
  13. package/ios/Sources/VideoPlayerPlugin/AVPlayerControlsVisibilityObserver.swift +13 -0
  14. package/ios/Sources/VideoPlayerPlugin/FullscreenVideoPlayer.swift +243 -33
  15. package/ios/Sources/VideoPlayerPlugin/SubtitleStyleOptions.swift +7 -0
  16. package/ios/Sources/VideoPlayerPlugin/VideoChapter.swift +45 -0
  17. package/ios/Sources/VideoPlayerPlugin/VideoPlayerAudioSessionCoordinator.swift +101 -0
  18. package/ios/Sources/VideoPlayerPlugin/VideoPlayerCastController.swift +23 -5
  19. package/ios/Sources/VideoPlayerPlugin/VideoPlayerPlugin.swift +49 -1
  20. package/ios/Tests/VideoPlayerPluginTests/VideoChapterTests.swift +30 -0
  21. package/ios/Tests/VideoPlayerPluginTests/VideoPlayerAudioSessionCategoryPolicyTests.swift +52 -0
  22. package/package.json +6 -4
@@ -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.2.14"
11
+ private let pluginVersion: String = "8.3.0"
12
12
  public let identifier = "VideoPlayerPlugin"
13
13
  public let jsName = "VideoPlayer"
14
14
  public let pluginMethods: [CAPPluginMethod] = [
@@ -76,6 +76,7 @@ extension VideoPlayerPlugin {
76
76
  let exitOnEnd = call.getBool("exitOnEnd") ?? true
77
77
  let loopOnEnd = call.getBool("loopOnEnd") ?? false
78
78
  let pipEnabled = call.getBool("pipEnabled") ?? true
79
+ let bkmodeEnabled = call.getBool("bkmodeEnabled") ?? true
79
80
  let showControls = call.getBool("showControls") ?? true
80
81
  let chromecast = call.getBool("chromecast") ?? true
81
82
  let title = call.getString("title")
@@ -84,6 +85,8 @@ extension VideoPlayerPlugin {
84
85
  let chromecastUrl = call.getString("chromecastUrl")
85
86
  let audioCategory = call.getString("audioCategory")
86
87
  let subtitleTracks = Self.parseSubtitleTracks(from: call)
88
+ let subtitleStyleOptions = Self.parseSubtitleStyleOptions(from: call)
89
+ let chapters = Self.parseChapters(from: call)
87
90
  let drmOptions = Self.parseDrmOptions(from: call)
88
91
 
89
92
  // Create video player
@@ -94,6 +97,7 @@ extension VideoPlayerPlugin {
94
97
  exitOnEnd: exitOnEnd,
95
98
  loopOnEnd: loopOnEnd,
96
99
  pipEnabled: pipEnabled,
100
+ bkmodeEnabled: bkmodeEnabled,
97
101
  showControls: showControls,
98
102
  chromecast: chromecast,
99
103
  chromecastUrl: chromecastUrl,
@@ -101,6 +105,8 @@ extension VideoPlayerPlugin {
101
105
  smallTitle: smallTitle,
102
106
  artwork: artwork,
103
107
  subtitleTracks: subtitleTracks,
108
+ subtitleStyleOptions: subtitleStyleOptions,
109
+ chapters: chapters,
104
110
  fairplayCertificateUrl: drmOptions.fairplayCertificateUrl,
105
111
  fairplayContentKeySpcUrl: drmOptions.fairplayContentKeySpcUrl,
106
112
  fairplayAssetId: drmOptions.fairplayAssetId,
@@ -137,6 +143,48 @@ extension VideoPlayerPlugin {
137
143
  }
138
144
  }
139
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
+
140
188
  private static func parseSubtitleTracks(from call: CAPPluginCall) -> [VideoSubtitleTrack] {
141
189
  let subtitlesArray = call.getArray("subtitles", JSObject.self)?.map { entry -> [String: Any] in
142
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
+ }
@@ -0,0 +1,52 @@
1
+ import XCTest
2
+ @testable import VideoPlayerPlugin
3
+
4
+ final class VideoPlayerAudioSessionCategoryPolicyTests: XCTestCase {
5
+ func testPerPlayerCategoryUsesExplicitCategoryWhenSet() {
6
+ let needs = VideoPlayerAudioSessionNeeds(
7
+ explicitCategory: "moviePlayback",
8
+ needsBackgroundOrPipPlayback: false
9
+ )
10
+ XCTAssertEqual(VideoPlayerAudioSessionCategoryPolicy.category(for: needs), "moviePlayback")
11
+ }
12
+
13
+ func testPerPlayerCategoryUsesPlaybackForBackgroundOrPip() {
14
+ let needs = VideoPlayerAudioSessionNeeds(
15
+ explicitCategory: nil,
16
+ needsBackgroundOrPipPlayback: true
17
+ )
18
+ XCTAssertEqual(VideoPlayerAudioSessionCategoryPolicy.category(for: needs), "playback")
19
+ }
20
+
21
+ func testUnionCategoryStaysAmbientWhenNoPlayerNeedsPlayback() {
22
+ let ambient = VideoPlayerAudioSessionNeeds(
23
+ explicitCategory: nil,
24
+ needsBackgroundOrPipPlayback: false
25
+ )
26
+ XCTAssertEqual(VideoPlayerAudioSessionCategoryPolicy.unionCategory(for: [ambient, ambient]), "ambient")
27
+ }
28
+
29
+ func testUnionCategoryUpgradesToPlaybackWhenAnyPlayerNeedsBackgroundOrPip() {
30
+ let ambient = VideoPlayerAudioSessionNeeds(
31
+ explicitCategory: nil,
32
+ needsBackgroundOrPipPlayback: false
33
+ )
34
+ let pip = VideoPlayerAudioSessionNeeds(
35
+ explicitCategory: nil,
36
+ needsBackgroundOrPipPlayback: true
37
+ )
38
+ XCTAssertEqual(VideoPlayerAudioSessionCategoryPolicy.unionCategory(for: [ambient, pip]), "playback")
39
+ }
40
+
41
+ func testUnionCategoryPrefersMoviePlaybackOverPlayback() {
42
+ let movie = VideoPlayerAudioSessionNeeds(
43
+ explicitCategory: "moviePlayback",
44
+ needsBackgroundOrPipPlayback: false
45
+ )
46
+ let pip = VideoPlayerAudioSessionNeeds(
47
+ explicitCategory: nil,
48
+ needsBackgroundOrPipPlayback: true
49
+ )
50
+ XCTAssertEqual(VideoPlayerAudioSessionCategoryPolicy.unionCategory(for: [movie, pip]), "moviePlayback")
51
+ }
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-video-player",
3
- "version": "8.2.14",
3
+ "version": "8.3.0",
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.49.1",
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",