react-native-nitro-player 0.5.6 → 0.5.7

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 (31) hide show
  1. package/README.md +2 -0
  2. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +43 -0
  3. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +340 -0
  4. package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +60 -0
  5. package/ios/HybridTrackPlayer.swift +54 -1
  6. package/ios/core/TrackPlayerCore.swift +254 -2
  7. package/ios/playlist/PlaylistManager.swift +68 -0
  8. package/lib/specs/TrackPlayer.nitro.d.ts +47 -0
  9. package/lib/types/PlayerQueue.d.ts +5 -0
  10. package/nitrogen/generated/android/NitroPlayerOnLoad.cpp +2 -0
  11. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double.hpp +104 -0
  12. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +160 -0
  13. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +8 -0
  14. package/nitrogen/generated/android/c++/JPlayerConfig.hpp +7 -3
  15. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_TrackItem__double.kt +80 -0
  16. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +37 -0
  17. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/PlayerConfig.kt +6 -3
  18. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.cpp +16 -0
  19. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +65 -0
  20. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +62 -0
  21. package/nitrogen/generated/ios/swift/Func_void_double.swift +47 -0
  22. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double.swift +47 -0
  23. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +8 -0
  24. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +173 -0
  25. package/nitrogen/generated/ios/swift/PlayerConfig.swift +24 -1
  26. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +8 -0
  27. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +8 -0
  28. package/nitrogen/generated/shared/c++/PlayerConfig.hpp +6 -2
  29. package/package.json +1 -1
  30. package/src/specs/TrackPlayer.nitro.ts +57 -0
  31. package/src/types/PlayerQueue.ts +5 -0
package/README.md CHANGED
@@ -50,6 +50,8 @@ npm install react-native-nitro-modules
50
50
  | `skipToNext()` | Both | Skips to the next track in the queue. |
51
51
  | `skipToPrevious()` | Both | Skips to the previous track. |
52
52
  | `seek(position)` | Both | Seeks to a specific time position in seconds. |
53
+ | `setPlaybackSpeed(speed)` | Both | **Async**. Sets playback speed (e.g. 0.5x, 1x, 1.5x, 2x). |
54
+ | `getPlaybackSpeed()` | Both | **Async**. Gets the current playback speed. |
53
55
  | `setVolume(0-100)` | Both | Sets playback volume (0-100). |
54
56
  | `setRepeatMode(mode)` | Both | Sets repeat mode (`off`, `track`, `Playlist`). |
55
57
  | `addToUpNext(id)` | Both | **Async**. Adds a track to the "up next" queue (FIFO). |
@@ -106,6 +106,7 @@ class HybridTrackPlayer : HybridTrackPlayerSpec() {
106
106
  androidAutoEnabled = config.androidAutoEnabled,
107
107
  carPlayEnabled = config.carPlayEnabled,
108
108
  showInNotification = config.showInNotification,
109
+ lookaheadCount = config.lookaheadCount?.toInt(),
109
110
  )
110
111
  }
111
112
 
@@ -127,4 +128,46 @@ class HybridTrackPlayer : HybridTrackPlayerSpec() {
127
128
  Promise.async {
128
129
  core.skipToIndex(index.toInt())
129
130
  }
131
+
132
+ override fun updateTracks(tracks: Array<TrackItem>): Promise<Unit> =
133
+ Promise.async {
134
+ core.updateTracks(tracks.toList())
135
+ }
136
+
137
+ override fun getTracksById(trackIds: Array<String>): Promise<Array<TrackItem>> =
138
+ Promise.async {
139
+ core.getTracksById(trackIds.toList()).toTypedArray()
140
+ }
141
+
142
+ override fun getTracksNeedingUrls(): Promise<Array<TrackItem>> =
143
+ Promise.async {
144
+ core.getTracksNeedingUrls().toTypedArray()
145
+ }
146
+
147
+ override fun getNextTracks(count: Double): Promise<Array<TrackItem>> =
148
+ Promise.async {
149
+ core.getNextTracks(count.toInt()).toTypedArray()
150
+ }
151
+
152
+ override fun getCurrentTrackIndex(): Promise<Double> =
153
+ Promise.async {
154
+ core.getCurrentTrackIndex().toDouble()
155
+ }
156
+
157
+ override fun setPlaybackSpeed(speed: Double): Promise<Unit> =
158
+ Promise.async {
159
+ core.setPlayBackSpeed(speed)
160
+ Unit
161
+ }
162
+
163
+ override fun getPlaybackSpeed(): Promise<Double> =
164
+ Promise.async {
165
+ core.getPlayBackSpeed()
166
+ }
167
+
168
+ override fun onTracksNeedUpdate(callback: (tracks: Array<TrackItem>, lookahead: Double) -> Unit) {
169
+ core.addOnTracksNeedUpdateListener { tracks, lookahead ->
170
+ callback(tracks.toTypedArray(), lookahead.toDouble())
171
+ }
172
+ }
130
173
  }
@@ -83,6 +83,7 @@ class TrackPlayerCore private constructor(
83
83
  Collections.synchronizedList(mutableListOf<WeakCallbackBox<(Double, Double, Boolean?) -> Unit>>())
84
84
 
85
85
  private var currentRepeatMode: RepeatMode = RepeatMode.OFF
86
+ private var lookaheadCount: Int = 5 // Number of tracks to preload ahead
86
87
 
87
88
  // Temporary tracks for addToUpNext and playNext
88
89
  private var playNextStack: MutableList<TrackItem> = mutableListOf() // LIFO - last added plays first
@@ -283,6 +284,9 @@ class TrackPlayerCore private constructor(
283
284
  }
284
285
  notifyTrackChange(track, r)
285
286
  mediaSessionManager?.onTrackChanged()
287
+
288
+ // Check if upcoming tracks need URLs
289
+ checkUpcomingTracksForUrls(lookahead = lookaheadCount)
286
290
  }
287
291
  }
288
292
 
@@ -380,6 +384,9 @@ class TrackPlayerCore private constructor(
380
384
  if (playlist != null) {
381
385
  currentPlaylistId = playlistId
382
386
  updatePlayerQueue(playlist.tracks)
387
+
388
+ // Check if upcoming tracks need URLs
389
+ checkUpcomingTracksForUrls(lookahead = lookaheadCount)
383
390
  }
384
391
  }
385
392
  }
@@ -641,6 +648,9 @@ class TrackPlayerCore private constructor(
641
648
  handler.post {
642
649
  if (player.hasNextMediaItem()) {
643
650
  player.seekToNextMediaItem()
651
+
652
+ // Check if upcoming tracks need URLs
653
+ checkUpcomingTracksForUrls(lookahead = lookaheadCount)
644
654
  }
645
655
  }
646
656
  }
@@ -684,6 +694,9 @@ class TrackPlayerCore private constructor(
684
694
  NitroPlayerLogger.log("TrackPlayerCore", "🔄 TrackPlayerCore: Already at first track, seeking to beginning")
685
695
  player.seekTo(0)
686
696
  }
697
+
698
+ // Check if upcoming tracks need URLs
699
+ checkUpcomingTracksForUrls(lookahead = lookaheadCount)
687
700
  }
688
701
  }
689
702
 
@@ -811,11 +824,16 @@ class TrackPlayerCore private constructor(
811
824
  androidAutoEnabled: Boolean?,
812
825
  carPlayEnabled: Boolean?,
813
826
  showInNotification: Boolean?,
827
+ lookaheadCount: Int? = null,
814
828
  ) {
815
829
  handler.post {
816
830
  androidAutoEnabled?.let {
817
831
  NitroPlayerMediaBrowserService.isAndroidAutoEnabled = it
818
832
  }
833
+ lookaheadCount?.let {
834
+ this.lookaheadCount = it
835
+ NitroPlayerLogger.log("TrackPlayerCore", "🔄 Lookahead count set to: $it")
836
+ }
819
837
  mediaSessionManager?.configure(
820
838
  androidAutoEnabled,
821
839
  carPlayEnabled,
@@ -1015,9 +1033,16 @@ class TrackPlayerCore private constructor(
1015
1033
  currentTemporaryType = TemporaryType.NONE
1016
1034
 
1017
1035
  rebuildQueueAndPlayFromIndex(originalIndex)
1036
+
1037
+ // Check if upcoming tracks need URLs
1038
+ checkUpcomingTracksForUrls(lookahead = lookaheadCount)
1039
+
1018
1040
  return true
1019
1041
  }
1020
1042
 
1043
+ // Check if upcoming tracks need URLs after any successful skip
1044
+ checkUpcomingTracksForUrls(lookahead = lookaheadCount)
1045
+
1021
1046
  return false
1022
1047
  }
1023
1048
 
@@ -1468,4 +1493,319 @@ class TrackPlayerCore private constructor(
1468
1493
 
1469
1494
  return queue
1470
1495
  }
1496
+
1497
+ // MARK: - Lazy URL Loading Support
1498
+
1499
+ /**
1500
+ * Update entire track objects and rebuild queue if needed
1501
+ * Skips currently playing track to preserve gapless playback
1502
+ */
1503
+ fun updateTracks(tracks: List<TrackItem>) {
1504
+ handler.post {
1505
+ NitroPlayerLogger.log("TrackPlayerCore", "🔄 updateTracks: ${tracks.size} updates")
1506
+
1507
+ // Get current track ID to avoid updating it (preserves gapless playback)
1508
+ val currentTrackId = getCurrentTrack()?.id
1509
+
1510
+ // Filter out current track and validate
1511
+ val safeTracks =
1512
+ tracks.filter { track ->
1513
+ when {
1514
+ track.id == currentTrackId -> {
1515
+ NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Skipping update for currently playing track: ${track.id} (preserves gapless)")
1516
+ false
1517
+ }
1518
+
1519
+ track.url.isEmpty() -> {
1520
+ NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Skipping track with empty URL: ${track.id}")
1521
+ false
1522
+ }
1523
+
1524
+ else -> {
1525
+ true
1526
+ }
1527
+ }
1528
+ }
1529
+
1530
+ if (safeTracks.isEmpty()) {
1531
+ NitroPlayerLogger.log("TrackPlayerCore", "✅ No valid updates to apply")
1532
+ return@post
1533
+ }
1534
+
1535
+ // Update in PlaylistManager
1536
+ val affectedPlaylists = playlistManager.updateTracks(safeTracks)
1537
+
1538
+ // Rebuild queue if current playlist was affected
1539
+ if (currentPlaylistId != null && affectedPlaylists.containsKey(currentPlaylistId)) {
1540
+ NitroPlayerLogger.log("TrackPlayerCore", "🔄 Rebuilding queue - ${affectedPlaylists[currentPlaylistId]} tracks updated in current playlist")
1541
+
1542
+ // This method preserves current item and gapless buffering
1543
+ rebuildQueueFromCurrentPosition()
1544
+
1545
+ NitroPlayerLogger.log("TrackPlayerCore", "✅ Queue rebuilt, gapless playback preserved")
1546
+ }
1547
+
1548
+ NitroPlayerLogger.log("TrackPlayerCore", "✅ Track updates complete - ${affectedPlaylists.size} playlists affected")
1549
+ }
1550
+ }
1551
+
1552
+ /**
1553
+ * Get tracks by IDs from all playlists
1554
+ */
1555
+ fun getTracksById(trackIds: List<String>): List<TrackItem> {
1556
+ if (android.os.Looper.myLooper() == handler.looper) {
1557
+ return playlistManager.getTracksById(trackIds)
1558
+ }
1559
+
1560
+ val latch = CountDownLatch(1)
1561
+ var result: List<TrackItem>? = null
1562
+
1563
+ handler.post {
1564
+ try {
1565
+ result = playlistManager.getTracksById(trackIds)
1566
+ } finally {
1567
+ latch.countDown()
1568
+ }
1569
+ }
1570
+
1571
+ try {
1572
+ latch.await(5, TimeUnit.SECONDS)
1573
+ } catch (e: InterruptedException) {
1574
+ Thread.currentThread().interrupt()
1575
+ }
1576
+
1577
+ return result ?: emptyList()
1578
+ }
1579
+
1580
+ /**
1581
+ * Get tracks needing URLs from current playlist
1582
+ */
1583
+ fun getTracksNeedingUrls(): List<TrackItem> {
1584
+ if (android.os.Looper.myLooper() == handler.looper) {
1585
+ return getTracksNeedingUrlsInternal()
1586
+ }
1587
+
1588
+ val latch = CountDownLatch(1)
1589
+ var result: List<TrackItem>? = null
1590
+
1591
+ handler.post {
1592
+ try {
1593
+ result = getTracksNeedingUrlsInternal()
1594
+ } finally {
1595
+ latch.countDown()
1596
+ }
1597
+ }
1598
+
1599
+ try {
1600
+ latch.await(5, TimeUnit.SECONDS)
1601
+ } catch (e: InterruptedException) {
1602
+ Thread.currentThread().interrupt()
1603
+ }
1604
+
1605
+ return result ?: emptyList()
1606
+ }
1607
+
1608
+ private fun getTracksNeedingUrlsInternal(): List<TrackItem> {
1609
+ if (currentPlaylistId == null) return emptyList()
1610
+
1611
+ val playlist = playlistManager.getPlaylist(currentPlaylistId!!)
1612
+ return playlist?.tracks?.filter { it.url.isEmpty() } ?: emptyList()
1613
+ }
1614
+
1615
+ /**
1616
+ * Get next N tracks from current position
1617
+ */
1618
+ fun getNextTracks(count: Int): List<TrackItem> {
1619
+ if (android.os.Looper.myLooper() == handler.looper) {
1620
+ return getNextTracksInternal(count)
1621
+ }
1622
+
1623
+ val latch = CountDownLatch(1)
1624
+ var result: List<TrackItem>? = null
1625
+
1626
+ handler.post {
1627
+ try {
1628
+ result = getNextTracksInternal(count)
1629
+ } finally {
1630
+ latch.countDown()
1631
+ }
1632
+ }
1633
+
1634
+ try {
1635
+ latch.await(5, TimeUnit.SECONDS)
1636
+ } catch (e: InterruptedException) {
1637
+ Thread.currentThread().interrupt()
1638
+ }
1639
+
1640
+ return result ?: emptyList()
1641
+ }
1642
+
1643
+ private fun getNextTracksInternal(count: Int): List<TrackItem> {
1644
+ val actualQueue = getActualQueueInternal()
1645
+ if (actualQueue.isEmpty()) return emptyList()
1646
+
1647
+ val currentIndex = actualQueue.indexOfFirst { it.id == getCurrentTrack()?.id }
1648
+ if (currentIndex == -1) return emptyList()
1649
+
1650
+ val startIndex = currentIndex + 1
1651
+ val endIndex = minOf(startIndex + count, actualQueue.size)
1652
+
1653
+ return if (startIndex < actualQueue.size) {
1654
+ actualQueue.subList(startIndex, endIndex)
1655
+ } else {
1656
+ emptyList()
1657
+ }
1658
+ }
1659
+
1660
+ /**
1661
+ * Get current track index in playlist
1662
+ */
1663
+ fun getCurrentTrackIndex(): Int {
1664
+ if (android.os.Looper.myLooper() == handler.looper) {
1665
+ return currentTrackIndex
1666
+ }
1667
+
1668
+ val latch = CountDownLatch(1)
1669
+ var result = -1
1670
+
1671
+ handler.post {
1672
+ try {
1673
+ result = currentTrackIndex
1674
+ } finally {
1675
+ latch.countDown()
1676
+ }
1677
+ }
1678
+
1679
+ try {
1680
+ latch.await(5, TimeUnit.SECONDS)
1681
+ } catch (e: InterruptedException) {
1682
+ Thread.currentThread().interrupt()
1683
+ }
1684
+
1685
+ return result
1686
+ }
1687
+
1688
+ /**
1689
+ * Callback interface for tracks needing update
1690
+ */
1691
+ fun interface OnTracksNeedUpdateListener {
1692
+ fun onTracksNeedUpdate(
1693
+ tracks: List<TrackItem>,
1694
+ lookahead: Int,
1695
+ )
1696
+ }
1697
+
1698
+ // Add to class properties
1699
+ private val onTracksNeedUpdateListeners = mutableListOf<WeakReference<OnTracksNeedUpdateListener>>()
1700
+
1701
+ /**
1702
+ * Register listener for when tracks need update
1703
+ */
1704
+ fun addOnTracksNeedUpdateListener(listener: OnTracksNeedUpdateListener) {
1705
+ handler.post {
1706
+ onTracksNeedUpdateListeners.add(WeakReference(listener))
1707
+ }
1708
+ }
1709
+
1710
+ /**
1711
+ * Remove listener
1712
+ */
1713
+ fun removeOnTracksNeedUpdateListener(listener: OnTracksNeedUpdateListener) {
1714
+ handler.post {
1715
+ onTracksNeedUpdateListeners.removeAll { it.get() == listener || it.get() == null }
1716
+ }
1717
+ }
1718
+
1719
+ /**
1720
+ * Notify listeners that tracks need updating
1721
+ * Called internally when moving to next track and upcoming tracks have empty URLs
1722
+ */
1723
+ private fun notifyTracksNeedUpdate(
1724
+ tracks: List<TrackItem>,
1725
+ lookahead: Int,
1726
+ ) {
1727
+ val liveCallbacks =
1728
+ synchronized(onTracksNeedUpdateListeners) {
1729
+ onTracksNeedUpdateListeners.removeAll { it.get() == null }
1730
+ onTracksNeedUpdateListeners.mapNotNull { it.get() }
1731
+ }
1732
+
1733
+ handler.post {
1734
+ for (callback in liveCallbacks) {
1735
+ try {
1736
+ callback.onTracksNeedUpdate(tracks, lookahead)
1737
+ } catch (e: Exception) {
1738
+ NitroPlayerLogger.log("TrackPlayerCore", "⚠️ Error in onTracksNeedUpdate listener: ${e.message}")
1739
+ }
1740
+ }
1741
+ }
1742
+ }
1743
+
1744
+ /**
1745
+ * Check if upcoming tracks need URLs and notify listeners
1746
+ * Call this in onMediaItemTransition or after skipTo operations
1747
+ */
1748
+ private fun checkUpcomingTracksForUrls(lookahead: Int = 5) {
1749
+ val nextTracks = getNextTracksInternal(lookahead)
1750
+ val tracksNeedingUrls = nextTracks.filter { it.url.isEmpty() }
1751
+
1752
+ if (tracksNeedingUrls.isNotEmpty()) {
1753
+ NitroPlayerLogger.log("TrackPlayerCore", "⚠️ ${tracksNeedingUrls.size} upcoming tracks need URLs")
1754
+ notifyTracksNeedUpdate(tracksNeedingUrls, lookahead)
1755
+ }
1756
+ }
1757
+
1758
+ fun setPlayBackSpeed(speed: Double) {
1759
+ if (android.os.Looper.myLooper() == handler.looper) {
1760
+ setPlayBackSpeedInternal(speed)
1761
+ return
1762
+ }
1763
+ val latch = CountDownLatch(1)
1764
+ handler.post {
1765
+ try {
1766
+ setPlayBackSpeedInternal(speed)
1767
+ } finally {
1768
+ latch.countDown()
1769
+ }
1770
+ }
1771
+ try {
1772
+ latch.await(5, TimeUnit.SECONDS)
1773
+ } catch (e: InterruptedException) {
1774
+ Thread.currentThread().interrupt()
1775
+ }
1776
+ }
1777
+
1778
+ private fun setPlayBackSpeedInternal(speed: Double) {
1779
+ if (::player.isInitialized) {
1780
+ player.setPlaybackSpeed(speed.toFloat())
1781
+ }
1782
+ }
1783
+
1784
+ fun getPlayBackSpeed(): Double {
1785
+ if (android.os.Looper.myLooper() == handler.looper) {
1786
+ return getPlayBackSpeedInternal()
1787
+ }
1788
+ val latch = CountDownLatch(1)
1789
+ var result = 1.0
1790
+ handler.post {
1791
+ try {
1792
+ result = getPlayBackSpeedInternal()
1793
+ } finally {
1794
+ latch.countDown()
1795
+ }
1796
+ }
1797
+ try {
1798
+ latch.await(5, TimeUnit.SECONDS)
1799
+ } catch (e: InterruptedException) {
1800
+ Thread.currentThread().interrupt()
1801
+ }
1802
+ return result
1803
+ }
1804
+
1805
+ private fun getPlayBackSpeedInternal(): Double =
1806
+ if (::player.isInitialized) {
1807
+ player.playbackParameters.speed.toDouble()
1808
+ } else {
1809
+ 1.0
1810
+ }
1471
1811
  }
@@ -302,6 +302,66 @@ class PlaylistManager private constructor(
302
302
  return true
303
303
  }
304
304
 
305
+ /**
306
+ * Update entire track objects across all playlists
307
+ * Matches by track.id and replaces the entire track object
308
+ * @param tracks List of full TrackItem objects to update
309
+ * @return Map of playlistId -> count of tracks updated
310
+ */
311
+ fun updateTracks(tracks: List<TrackItem>): Map<String, Int> {
312
+ val tracksMap = tracks.associateBy { it.id }
313
+ val affectedPlaylists = mutableMapOf<String, Int>()
314
+
315
+ synchronized(playlists) {
316
+ playlists.forEach { (playlistId, playlist) ->
317
+ var updateCount = 0
318
+ val newTracks =
319
+ playlist.tracks
320
+ .map { track ->
321
+ tracksMap[track.id]?.also { updateCount++ } ?: track
322
+ }.toMutableList()
323
+
324
+ if (updateCount > 0) {
325
+ affectedPlaylists[playlistId] = updateCount
326
+ playlists[playlistId] = playlist.copy(tracks = newTracks)
327
+ }
328
+ }
329
+ }
330
+
331
+ if (affectedPlaylists.isNotEmpty()) {
332
+ scheduleSave()
333
+ affectedPlaylists.keys.forEach { playlistId ->
334
+ notifyPlaylistChanged(playlistId, QueueOperation.UPDATE)
335
+ }
336
+ notifyPlaylistsChanged(QueueOperation.UPDATE)
337
+ }
338
+
339
+ return affectedPlaylists
340
+ }
341
+
342
+ /**
343
+ * Get tracks by IDs from all playlists
344
+ * @param trackIds List of track IDs to fetch
345
+ * @return List of matching TrackItem objects
346
+ */
347
+ fun getTracksById(trackIds: List<String>): List<TrackItem> {
348
+ val trackIdSet = trackIds.toSet()
349
+ val foundTracks = mutableMapOf<String, TrackItem>()
350
+
351
+ synchronized(playlists) {
352
+ playlists.values.forEach { playlist ->
353
+ playlist.tracks.forEach { track ->
354
+ if (trackIdSet.contains(track.id) && !foundTracks.containsKey(track.id)) {
355
+ foundTracks[track.id] = track
356
+ }
357
+ }
358
+ }
359
+ }
360
+
361
+ // Return in same order as requested
362
+ return trackIds.mapNotNull { foundTracks[it] }
363
+ }
364
+
305
365
  /**
306
366
  * Load a playlist for playback (sets it as current)
307
367
  */
@@ -88,7 +88,8 @@ final class HybridTrackPlayer: HybridTrackPlayerSpec {
88
88
  core.configure(
89
89
  androidAutoEnabled: config.androidAutoEnabled,
90
90
  carPlayEnabled: config.carPlayEnabled,
91
- showInNotification: config.showInNotification
91
+ showInNotification: config.showInNotification,
92
+ lookaheadCount: config.lookaheadCount.map { Int($0) }
92
93
  )
93
94
  }
94
95
 
@@ -139,4 +140,56 @@ final class HybridTrackPlayer: HybridTrackPlayerSpec {
139
140
  func setVolume(volume: Double) throws -> Bool {
140
141
  return core.setVolume(volume: volume)
141
142
  }
143
+
144
+ // MARK: - Lazy URL Loading
145
+
146
+ func updateTracks(tracks: [TrackItem]) throws -> Promise<Void> {
147
+ return Promise.async {
148
+ self.core.updateTracks(tracks: tracks)
149
+ }
150
+ }
151
+
152
+ func getTracksById(trackIds: [String]) throws -> Promise<[TrackItem]> {
153
+ return Promise.async {
154
+ return self.core.getTracksById(trackIds: trackIds)
155
+ }
156
+ }
157
+
158
+ func getTracksNeedingUrls() throws -> Promise<[TrackItem]> {
159
+ return Promise.async {
160
+ return self.core.getTracksNeedingUrls()
161
+ }
162
+ }
163
+
164
+ func getNextTracks(count: Double) throws -> Promise<[TrackItem]> {
165
+ return Promise.async {
166
+ return self.core.getNextTracks(count: Int(count))
167
+ }
168
+ }
169
+
170
+ func getCurrentTrackIndex() throws -> Promise<Double> {
171
+ return Promise.async {
172
+ return Double(self.core.getCurrentTrackIndex())
173
+ }
174
+ }
175
+
176
+ func onTracksNeedUpdate(callback: @escaping ([TrackItem], Double) -> Void) throws {
177
+ core.addOnTracksNeedUpdateListener { tracks, lookahead in
178
+ callback(tracks, Double(lookahead))
179
+ }
180
+ }
181
+
182
+ func setPlaybackSpeed(speed: Double) throws -> Promise<Void> {
183
+ Promise.async{
184
+ self.core.setPlaybackSpeed(speed)
185
+ }
186
+
187
+ }
188
+
189
+ func getPlaybackSpeed() throws -> Promise<Double> {
190
+ return Promise.async{
191
+ return self.core.getPlaybackSpeed()
192
+ }
193
+
194
+ }
142
195
  }