capacitor-plugin-playlist 0.11.0 → 0.11.2

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 (43) hide show
  1. package/README.md +63 -6
  2. package/android/build.gradle +21 -0
  3. package/android/src/main/java/org/dwbn/plugins/playlist/PlaylistPlugin.kt +76 -19
  4. package/android/src/main/java/org/dwbn/plugins/playlist/RmxAudioStatusMessage.java +2 -0
  5. package/android/src/main/java/org/dwbn/plugins/playlist/data/AudioTrack.kt +7 -6
  6. package/android/src/main/java/org/dwbn/plugins/playlist/manager/PlaylistManager.kt +124 -30
  7. package/android/src/test/java/org/dwbn/plugins/playlist/manager/PlaylistManagerMoveTest.kt +76 -0
  8. package/android/src/test/java/org/dwbn/plugins/playlist/manager/PlaylistManagerReplaceTest.kt +57 -0
  9. package/dist/docs.json +111 -1
  10. package/dist/esm/Constants.d.ts +10 -0
  11. package/dist/esm/Constants.js +10 -0
  12. package/dist/esm/Constants.js.map +1 -1
  13. package/dist/esm/RmxAudioPlayer.d.ts +16 -2
  14. package/dist/esm/RmxAudioPlayer.js +28 -3
  15. package/dist/esm/RmxAudioPlayer.js.map +1 -1
  16. package/dist/esm/definitions.d.ts +28 -1
  17. package/dist/esm/web.d.ts +3 -1
  18. package/dist/esm/web.js +54 -2
  19. package/dist/esm/web.js.map +1 -1
  20. package/dist/plugin.cjs.js +92 -5
  21. package/dist/plugin.cjs.js.map +1 -1
  22. package/dist/plugin.js +92 -5
  23. package/dist/plugin.js.map +1 -1
  24. package/ios/Sources/PlaylistPlugin/Constants.swift +2 -0
  25. package/ios/Sources/PlaylistPlugin/Plugin.swift +52 -5
  26. package/ios/Sources/PlaylistPlugin/RmxAudioPlayer.swift +123 -1
  27. package/ios/Tests/PlaylistPluginTests/PlaylistMutationTests.swift +168 -0
  28. package/package.json +10 -4
  29. package/android/.gradle/8.14.3/checksums/checksums.lock +0 -0
  30. package/android/.gradle/8.14.3/checksums/md5-checksums.bin +0 -0
  31. package/android/.gradle/8.14.3/checksums/sha1-checksums.bin +0 -0
  32. package/android/.gradle/8.14.3/executionHistory/executionHistory.bin +0 -0
  33. package/android/.gradle/8.14.3/executionHistory/executionHistory.lock +0 -0
  34. package/android/.gradle/8.14.3/fileChanges/last-build.bin +0 -0
  35. package/android/.gradle/8.14.3/fileHashes/fileHashes.bin +0 -0
  36. package/android/.gradle/8.14.3/fileHashes/fileHashes.lock +0 -0
  37. package/android/.gradle/8.14.3/fileHashes/resourceHashesCache.bin +0 -0
  38. package/android/.gradle/8.14.3/gc.properties +0 -0
  39. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  40. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  41. package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
  42. package/android/.gradle/file-system.probe +0 -0
  43. package/android/.gradle/vcs-1/gc.properties +0 -0
@@ -0,0 +1,168 @@
1
+ import XCTest
2
+ @testable import PlaylistPlugin
3
+
4
+ /// Captures every onStatus(...) call made by an RmxAudioPlayer, so tests can assert exactly
5
+ /// which (and how many) status events a mutation produced.
6
+ private final class RecordingStatusUpdater: StatusUpdater {
7
+ private(set) var calls: [[String: Any]] = []
8
+
9
+ func onStatus(_ data: [String: Any]) {
10
+ calls.append(data)
11
+ }
12
+
13
+ private func msgType(of data: [String: Any]) -> Int? {
14
+ guard let status = data["status"] as? [String: Any], let msgType = status["msgType"] as? NSNumber else {
15
+ return nil
16
+ }
17
+ return msgType.intValue
18
+ }
19
+
20
+ func count(of message: RmxAudioStatusMessage) -> Int {
21
+ calls.filter { msgType(of: $0) == message.rawValue }.count
22
+ }
23
+ }
24
+
25
+ @MainActor
26
+ private func makeTrack(id: String, url: String = "https://example.com/track.mp3") -> AudioTrack {
27
+ AudioTrack.initWithDictionary([
28
+ "trackId": id,
29
+ "assetUrl": url,
30
+ "artist": "Artist",
31
+ "album": "Album",
32
+ "title": "Title \(id)"
33
+ ])!
34
+ }
35
+
36
+ /// Regression coverage for addItem/moveItem/replaceItem: the "insert/move/replace without
37
+ /// disrupting playback" contract documented in the README, and the specific event-flood bug
38
+ /// where moveItem/replaceItem/addItem(at: 0) used to rebuild the whole native queue via
39
+ /// setTracks(), re-firing RMXSTATUS_ITEM_ADDED for every already-known track.
40
+ @MainActor
41
+ final class PlaylistMutationTests: XCTestCase {
42
+
43
+ private func makePlayer(trackIds: [String]) -> (RmxAudioPlayer, RecordingStatusUpdater) {
44
+ let player = RmxAudioPlayer()
45
+ player.addAllItems(trackIds.map { makeTrack(id: $0) })
46
+ // Attach the recorder only after seeding, so setup noise doesn't pollute assertions.
47
+ let updater = RecordingStatusUpdater()
48
+ player.statusUpdater = updater
49
+ return (player, updater)
50
+ }
51
+
52
+ // MARK: - addItem
53
+
54
+ func testAddItemAtIndex_insertsAtCorrectPosition() throws {
55
+ let (player, updater) = makePlayer(trackIds: ["a", "b", "c"])
56
+
57
+ try player.addItem(makeTrack(id: "new"), at: 1)
58
+
59
+ XCTAssertEqual(player.avQueuePlayer.queuedAudioTracks.map { $0.trackId }, ["a", "new", "b", "c"])
60
+ XCTAssertEqual(updater.count(of: .rmxstatus_ITEM_ADDED), 1)
61
+ }
62
+
63
+ func testAddItemAtIndexZero_emitsExactlyOneItemAddedEvent() throws {
64
+ let (player, updater) = makePlayer(trackIds: ["a", "b", "c"])
65
+
66
+ try player.addItem(makeTrack(id: "new"), at: 0)
67
+
68
+ XCTAssertEqual(player.avQueuePlayer.queuedAudioTracks.map { $0.trackId }, ["new", "a", "b", "c"])
69
+ // Regression: addItem(at: 0) used to rebuild the whole queue via setTracks(), which
70
+ // re-fired ITEM_ADDED for every already-known track (4 events instead of 1).
71
+ XCTAssertEqual(updater.count(of: .rmxstatus_ITEM_ADDED), 1)
72
+ }
73
+
74
+ // MARK: - moveItem
75
+
76
+ func testMoveItem_reordersTracks() throws {
77
+ let (player, _) = makePlayer(trackIds: ["a", "b", "c", "d"])
78
+
79
+ try player.moveItem(from: 0, to: 2)
80
+
81
+ XCTAssertEqual(player.avQueuePlayer.queuedAudioTracks.map { $0.trackId }, ["b", "c", "a", "d"])
82
+ }
83
+
84
+ func testMoveItem_doesNotEmitItemAddedEvents() throws {
85
+ let (player, updater) = makePlayer(trackIds: ["a", "b", "c", "d"])
86
+
87
+ try player.moveItem(from: 0, to: 2)
88
+
89
+ // Regression: moveItem used to rebuild the queue via setTracks(), which re-registered
90
+ // observers and re-fired ITEM_ADDED for every track even though nothing was added.
91
+ XCTAssertEqual(updater.count(of: .rmxstatus_ITEM_ADDED), 0)
92
+ XCTAssertEqual(updater.count(of: .rmxstatus_ITEM_MOVED), 1)
93
+ }
94
+
95
+ func testMoveItem_outOfBoundsThrows() {
96
+ let (player, _) = makePlayer(trackIds: ["a", "b"])
97
+ XCTAssertThrowsError(try player.moveItem(from: 0, to: 5))
98
+ }
99
+
100
+ func testMoveItem_sameIndexIsANoOp() throws {
101
+ let (player, updater) = makePlayer(trackIds: ["a", "b"])
102
+
103
+ try player.moveItem(from: 1, to: 1)
104
+
105
+ XCTAssertEqual(player.avQueuePlayer.queuedAudioTracks.map { $0.trackId }, ["a", "b"])
106
+ XCTAssertEqual(updater.count(of: .rmxstatus_ITEM_MOVED), 0)
107
+ }
108
+
109
+ // MARK: - replaceItem
110
+
111
+ func testReplaceItemByIndex_preservesTrackIdWhenOmitted() throws {
112
+ let (player, updater) = makePlayer(trackIds: ["a", "b"])
113
+
114
+ try player.replaceItem(at: 1, id: nil, with: [
115
+ "assetUrl": "https://example.com/local-b.mp3",
116
+ "artist": "New Artist",
117
+ "album": "New Album",
118
+ "title": "New Title"
119
+ ])
120
+
121
+ let tracks = player.avQueuePlayer.queuedAudioTracks
122
+ XCTAssertEqual(tracks.map { $0.trackId }, ["a", "b"])
123
+ XCTAssertEqual(tracks[1].assetUrl?.absoluteString, "https://example.com/local-b.mp3")
124
+ XCTAssertEqual(updater.count(of: .rmxstatus_ITEM_REPLACED), 1)
125
+ }
126
+
127
+ func testReplaceItem_emitsExactlyOneReplacedEvent_noItemAddedFlood() throws {
128
+ let (player, updater) = makePlayer(trackIds: ["a", "b", "c"])
129
+
130
+ try player.replaceItem(at: 1, id: nil, with: [
131
+ "assetUrl": "https://example.com/new.mp3",
132
+ "artist": "Artist",
133
+ "album": "Album",
134
+ "title": "Title"
135
+ ])
136
+
137
+ // Regression: replaceItem used to rebuild the whole queue via setTracks(), which
138
+ // re-fired ITEM_ADDED for every still-current track (3 events instead of 0).
139
+ XCTAssertEqual(updater.count(of: .rmxstatus_ITEM_ADDED), 0)
140
+ XCTAssertEqual(updater.count(of: .rmxstatus_ITEM_REPLACED), 1)
141
+ }
142
+
143
+ func testReplaceItem_byId() throws {
144
+ let (player, _) = makePlayer(trackIds: ["a", "b", "c"])
145
+
146
+ try player.replaceItem(at: nil, id: "b", with: [
147
+ "assetUrl": "https://example.com/replaced.mp3",
148
+ "artist": "Artist",
149
+ "album": "Album",
150
+ "title": "Title"
151
+ ])
152
+
153
+ let tracks = player.avQueuePlayer.queuedAudioTracks
154
+ XCTAssertEqual(tracks.map { $0.trackId }, ["a", "b", "c"])
155
+ XCTAssertEqual(tracks[1].assetUrl?.absoluteString, "https://example.com/replaced.mp3")
156
+ }
157
+
158
+ func testReplaceItem_missingTargetThrows() {
159
+ let (player, _) = makePlayer(trackIds: ["a"])
160
+
161
+ XCTAssertThrowsError(try player.replaceItem(at: nil, id: "missing", with: [
162
+ "assetUrl": "https://example.com/x.mp3",
163
+ "artist": "Artist",
164
+ "album": "Album",
165
+ "title": "Title"
166
+ ]))
167
+ }
168
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-plugin-playlist",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Playlist ",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -27,6 +27,8 @@
27
27
  "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
28
28
  "clean": "rimraf ./dist",
29
29
  "watch": "tsc --watch",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
30
32
  "prepublishOnly": "npm run build"
31
33
  },
32
34
  "author": "phiamo",
@@ -39,17 +41,19 @@
39
41
  "@capacitor/core": "^8.0.0",
40
42
  "@capacitor/docgen": "^0.3.1",
41
43
  "@capacitor/ios": "^8.0.0",
44
+ "@ionic/eslint-config": "^0.4.0",
42
45
  "@ionic/prettier-config": "^4.0.0",
43
46
  "@ionic/swiftlint-config": "^2.0.0",
44
- "@ionic/eslint-config": "^0.4.0",
45
47
  "@types/hls.js": "^0.13.2",
46
48
  "eslint": "^8.57.1",
49
+ "jsdom": "^29.1.1",
47
50
  "prettier": "^3.6.2",
48
51
  "prettier-plugin-java": "^2.7.7",
49
52
  "rimraf": "^6.1.0",
50
53
  "rollup": "^4.53.2",
51
54
  "swiftlint": "^2.0.0",
52
- "typescript": "^5.9.3"
55
+ "typescript": "^5.9.3",
56
+ "vitest": "^4.1.10"
53
57
  },
54
58
  "peerDependencies": {
55
59
  "@capacitor/core": ">=8.0.0"
@@ -80,7 +84,9 @@
80
84
  "url": "https://github.com/phiamo/capacitor-plugin-playlist/issues"
81
85
  },
82
86
  "publishConfig": {
83
- "@phiamo:registry": "https://npm.pkg.github.com"
87
+ "registry": "https://registry.npmjs.org/",
88
+ "access": "public",
89
+ "provenance": true
84
90
  },
85
91
  "overrides": {
86
92
  "@types/node": "^20.0.0"
File without changes
@@ -1,2 +0,0 @@
1
- #Thu Dec 25 00:10:55 CET 2025
2
- gradle.version=8.14.3
Binary file
File without changes