Skip to content

Commit 350d594

Browse files
authored
Add a function to update a track metadata (doublesymmetry#465)
This PR adds the `updateMetadataForTrack(id, metadata)` function which allows you to update any track in the queue, including the current one. It returns a promise which resolves after the metadata has been successfully updated. Closes doublesymmetry#93 and supersedes doublesymmetry#331
1 parent bd657aa commit 350d594

File tree

16 files changed

+189
-29
lines changed

16 files changed

+189
-29
lines changed

android/src/main/java/com/guichaguri/trackplayer/module/MusicModule.java

+29
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import com.guichaguri.trackplayer.service.MusicService;
1717
import com.guichaguri.trackplayer.service.Utils;
1818
import com.guichaguri.trackplayer.service.models.Track;
19+
import com.guichaguri.trackplayer.service.player.ExoPlayback;
20+
1921
import java.util.ArrayDeque;
2022
import java.util.ArrayList;
2123
import java.util.HashMap;
@@ -241,6 +243,33 @@ public void remove(ReadableArray tracks, final Promise callback) {
241243
});
242244
}
243245

246+
@ReactMethod
247+
public void updateMetadataForTrack(String id, ReadableMap map, final Promise callback) {
248+
waitForConnection(() -> {
249+
ExoPlayback playback = binder.getPlayback();
250+
List<Track> queue = playback.getQueue();
251+
Track track = null;
252+
int index = -1;
253+
254+
for(int i = 0; i < queue.size(); i++) {
255+
track = queue.get(i);
256+
257+
if(track.id.equals(id)) {
258+
index = i;
259+
break;
260+
}
261+
}
262+
263+
if(index == -1) {
264+
callback.reject("track_not_in_queue", "No track found");
265+
} else {
266+
track.setMetadata(getReactApplicationContext(), Arguments.toBundle(map), binder.getRatingType());
267+
playback.updateTrack(index, track);
268+
callback.resolve(null);
269+
}
270+
});
271+
}
272+
244273
@ReactMethod
245274
public void removeUpcomingTracks(final Promise callback) {
246275
waitForConnection(() -> {

android/src/main/java/com/guichaguri/trackplayer/service/models/Track.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ public Track(Context context, Bundle bundle, int ratingType) {
9090

9191
contentType = bundle.getString("contentType");
9292
userAgent = bundle.getString("userAgent");
93+
94+
setMetadata(context, bundle, ratingType);
95+
96+
queueId = System.currentTimeMillis();
97+
originalItem = bundle;
98+
}
99+
100+
public void setMetadata(Context context, Bundle bundle, int ratingType) {
93101
artwork = Utils.getUri(context, bundle, "artwork");
94102

95103
title = bundle.getString("title");
@@ -100,9 +108,9 @@ public Track(Context context, Bundle bundle, int ratingType) {
100108
duration = Utils.toMillis(bundle.getDouble("duration", 0));
101109

102110
rating = Utils.getRating(bundle, "rating", ratingType);
103-
104-
queueId = System.currentTimeMillis();
105-
originalItem = bundle;
111+
112+
if (originalItem != null && originalItem != bundle)
113+
originalItem.putAll(bundle);
106114
}
107115

108116
public MediaMetadataCompat.Builder toMediaMetadata() {

android/src/main/java/com/guichaguri/trackplayer/service/player/ExoPlayback.java

+9
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ public List<Track> getQueue() {
5959

6060
public abstract void removeUpcomingTracks();
6161

62+
public void updateTrack(int index, Track track) {
63+
int currentIndex = player.getCurrentWindowIndex();
64+
65+
queue.set(index, track);
66+
67+
if(currentIndex == index)
68+
manager.getMetadata().updateMetadata(track);
69+
}
70+
6271
public Track getCurrentTrack() {
6372
int index = player.getCurrentWindowIndex();
6473
return index == C.INDEX_UNSET || index < 0 || index >= queue.size() ? null : queue.get(index);

docs/Documentation.md

+11
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,17 @@ Gets the whole queue
207207
#### `removeUpcomingTracks()`
208208
Clears any upcoming tracks from the queue.
209209

210+
#### `updateMetadataForTrack(id, metadata)`
211+
Updates the metadata of a track in the queue.
212+
If the current track is updated, the notification and the Now Playing Center will be updated accordingly.
213+
214+
**Returns:** `Promise`
215+
216+
| Param | Type | Description |
217+
| -------- | ---------- | ------------- |
218+
| id | `string` | The track ID |
219+
| metadata | `object` | A subset of the [Track Object](#track-object) with only the `artwork`, `title`, `artist`, `album`, `description`, `genre`, `date`, `rating` and `duration` properties. |
220+
210221
### Player Functions
211222
#### `updateOptions(options)`
212223
Updates the configuration for the components.

index.d.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,7 @@ declare namespace RNTrackPlayer {
4545
type EmitterSubscription = { remove: () => void; };
4646
export function addEventListener(type: EventType, listener: (data: any) => void): EmitterSubscription;
4747

48-
export interface Track {
49-
id: string;
50-
url: string | ResourceObject;
51-
type?: TrackType;
52-
userAgent?: string;
53-
contentType?: string;
48+
export interface TrackMetadata {
5449
duration?: number;
5550
title: string;
5651
artist: string;
@@ -60,6 +55,14 @@ declare namespace RNTrackPlayer {
6055
date?: string;
6156
rating?: number | boolean;
6257
artwork?: string | ResourceObject;
58+
}
59+
60+
export interface Track extends TrackMetadata {
61+
id: string;
62+
url: string | ResourceObject;
63+
type?: TrackType;
64+
userAgent?: string;
65+
contentType?: string;
6366
pitchAlgorithm?: PitchAlgorithm;
6467
[key: string]: any;
6568
}
@@ -108,6 +111,7 @@ declare namespace RNTrackPlayer {
108111
export function skip(trackId: string): Promise<void>;
109112
export function skipToNext(): Promise<void>;
110113
export function skipToPrevious(): Promise<void>;
114+
export function updateMetadataForTrack(id: string, metadata: TrackMetadata) : Promise<void>;
111115
export function removeUpcomingTracks(): Promise<void>;
112116

113117
// Player Playback Commands

ios/RNTrackPlayer/Models/Track.swift

+23-9
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@ import AVFoundation
1313
class Track: NSObject, AudioItem, TimePitching {
1414
let id: String
1515
let url: MediaURL
16-
@objc let title: String
17-
@objc let artist: String
16+
@objc var title: String
17+
@objc var artist: String
1818

19-
let date: String?
20-
let desc: String?
21-
let genre: String?
19+
var date: String?
20+
var desc: String?
21+
var genre: String?
2222
let pitchAlgorithm: String?
23-
let duration: Double?
24-
let artworkURL: MediaURL?
23+
var duration: Double?
24+
var artworkURL: MediaURL?
2525
var skipped: Bool = false
26-
@objc let album: String?
26+
@objc var album: String?
2727
@objc var artwork: MPMediaItemArtwork?
2828

29-
private let originalObject: [String: Any]
29+
private var originalObject: [String: Any]
3030

3131
init?(dictionary: [String: Any]) {
3232
guard let id = dictionary["id"] as? String,
@@ -58,6 +58,20 @@ class Track: NSObject, AudioItem, TimePitching {
5858
return originalObject
5959
}
6060

61+
func updateMetadata(dictionary: [String: Any]) {
62+
self.title = (dictionary["title"] as? String) ?? self.title
63+
self.artist = (dictionary["artist"] as? String) ?? self.artist
64+
65+
self.date = dictionary["date"] as? String
66+
self.album = dictionary["album"] as? String
67+
self.genre = dictionary["genre"] as? String
68+
self.desc = dictionary["description"] as? String
69+
self.duration = dictionary["duration"] as? Double
70+
self.artworkURL = MediaURL(object: dictionary["artwork"])
71+
72+
self.originalObject = self.originalObject.merging(dictionary) { (_, new) in new }
73+
}
74+
6175
// MARK: - AudioItem Protocol
6276

6377
func getSourceUrl() -> String {

ios/RNTrackPlayer/RNTrackPlayer.swift

+28-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public class RNTrackPlayer: RCTEventEmitter {
107107
}
108108

109109
let sessionCategoryOptsStr = config["iosCategoryOptions"] as? [String]
110-
let mappedCategoryOpts = sessionCategoryOpts.compactMap { SessionCategoryOptions(rawValue: $0)?.mapConfigToAVAudioSessionCategoryOptions() }
110+
let mappedCategoryOpts = sessionCategoryOptsStr?.compactMap { SessionCategoryOptions(rawValue: $0)?.mapConfigToAVAudioSessionCategoryOptions() } ?? []
111111
sessionCategoryOptions = AVAudioSession.CategoryOptions(mappedCategoryOpts)
112112

113113
if
@@ -453,4 +453,31 @@ public class RNTrackPlayer: RCTEventEmitter {
453453
public func getState(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
454454
resolve(player.playerState.rawValue)
455455
}
456+
457+
@objc(updateMetadataForTrack:properties:resolver:rejecter:)
458+
public func updateMetadata(for trackId: String, properties: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
459+
guard let track = player.queueManager.items.first(where: { ($0 as! Track).id == trackId }) as? Track
460+
else {
461+
reject("track_not_in_queue", "Given track ID was not found in queue", nil)
462+
return
463+
}
464+
465+
track.updateMetadata(dictionary: properties)
466+
if (player.currentItem as! Track).id == track.id {
467+
player.nowPlayingInfoController.set(keyValues: [
468+
MediaItemProperty.artist(track.artist),
469+
MediaItemProperty.title(track.title),
470+
MediaItemProperty.albumTitle(track.album),
471+
])
472+
473+
track.getArtwork { [weak self] image in
474+
if let image = image {
475+
let artwork = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { (size) -> UIImage in
476+
return image
477+
})
478+
self?.player.nowPlayingInfoController.set(keyValue: MediaItemProperty.artwork(artwork))
479+
}
480+
}
481+
}
482+
}
456483
}

ios/RNTrackPlayer/RNTrackPlayerBridge.m

+5
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,9 @@ @interface RCT_EXTERN_REMAP_MODULE(TrackPlayerModule, RNTrackPlayer, NSObject)
9595
RCT_EXTERN_METHOD(getState:(RCTPromiseResolveBlock)resolve
9696
rejecter:(RCTPromiseRejectBlock)reject);
9797

98+
RCT_EXTERN_METHOD(updateMetadataForTrack:(NSString *)trackId
99+
withProperties:(NSDictionary *)properties
100+
resolver:(RCTPromiseResolveBlock)resolve
101+
rejecter:(RCTPromiseRejectBlock)reject);
102+
98103
@end

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ module.exports.skip = TrackPlayer.skip;
170170
module.exports.getQueue = TrackPlayer.getQueue;
171171
module.exports.skipToNext = TrackPlayer.skipToNext;
172172
module.exports.skipToPrevious = TrackPlayer.skipToPrevious;
173+
module.exports.updateMetadataForTrack = TrackPlayer.updateMetadataForTrack;
173174
module.exports.removeUpcomingTracks = TrackPlayer.removeUpcomingTracks;
174175

175176
// Player Playback Commands

windows/RNTrackPlayer.sln

+6-5
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ VisualStudioVersion = 15.0.26730.16
44
MinimumVisualStudioVersion = 10.0.40219.1
55
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RNTrackPlayer", "RNTrackPlayer\RNTrackPlayer.csproj", "{E292F490-9FB8-11E7-B023-0F938699DB28}"
66
EndProject
7-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactNative", "..\..\..\node_modules\react-native-windows\ReactWindows\ReactNative\ReactNative.csproj", "{C7673AD5-E3AA-468C-A5FD-FA38154E205C}"
7+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactNative", "..\..\node_modules\react-native-windows\ReactWindows\ReactNative\ReactNative.csproj", "{C7673AD5-E3AA-468C-A5FD-FA38154E205C}"
88
EndProject
9-
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "ReactNative.Shared", "..\..\..\node_modules\react-native-windows\ReactWindows\ReactNative.Shared\ReactNative.Shared.shproj", "{EEA8B852-4D07-48E1-8294-A21AB5909FE5}"
9+
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "ReactNative.Shared", "..\..\node_modules\react-native-windows\ReactWindows\ReactNative.Shared\ReactNative.Shared.shproj", "{EEA8B852-4D07-48E1-8294-A21AB5909FE5}"
1010
EndProject
11-
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ChakraBridge", "..\..\..\node_modules\react-native-windows\ReactWindows\ChakraBridge\ChakraBridge.vcxproj", "{4B72C796-16D5-4E3A-81C0-3E36F531E578}"
11+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ChakraBridge", "..\..\node_modules\react-native-windows\ReactWindows\ChakraBridge\ChakraBridge.vcxproj", "{4B72C796-16D5-4E3A-81C0-3E36F531E578}"
1212
EndProject
1313
Global
1414
GlobalSection(SharedMSBuildProjectFiles) = preSolution
15-
..\node_modules\react-native-windows\ReactWindows\ReactNative.Shared\ReactNative.Shared.projitems*{c7673ad5-e3aa-468c-a5fd-fa38154e205c}*SharedItemsImports = 4
16-
..\node_modules\react-native-windows\ReactWindows\ReactNative.Shared\ReactNative.Shared.projitems*{eea8b852-4d07-48e1-8294-a21ab5909fe5}*SharedItemsImports = 13
15+
..\..\node_modules\react-native-windows\ReactWindows\ReactNative.Shared\ReactNative.Shared.projitems*{c7673ad5-e3aa-468c-a5fd-fa38154e205c}*SharedItemsImports = 4
16+
..\..\node_modules\react-native-windows\Yoga\csharp\Facebook.Yoga\Facebook.Yoga.Shared.projitems*{c7673ad5-e3aa-468c-a5fd-fa38154e205c}*SharedItemsImports = 4
17+
..\..\node_modules\react-native-windows\ReactWindows\ReactNative.Shared\ReactNative.Shared.projitems*{eea8b852-4d07-48e1-8294-a21ab5909fe5}*SharedItemsImports = 13
1718
EndGlobalSection
1819
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1920
Debug|ARM = Debug|ARM

windows/RNTrackPlayer/Logic/MediaManager.cs

+5
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ public Playback GetPlayer()
5252
return player;
5353
}
5454

55+
public Metadata GetMetadata()
56+
{
57+
return metadata;
58+
}
59+
5560
public void OnEnd(Track previous, double prevPos)
5661
{
5762
Debug.WriteLine("OnEnd");

windows/RNTrackPlayer/Logic/Metadata.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace TrackPlayer.Logic
77
{
8-
class Metadata
8+
public class Metadata
99
{
1010
private MediaManager manager;
1111
private SystemMediaTransportControls controls;

windows/RNTrackPlayer/Logic/Track.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,23 @@ public Track(JObject data)
2222
Id = (string)data.GetValue("id");
2323
Url = Utils.GetUri(data, "url", null);
2424
Type = Utils.GetValue<string>(data, "type", TrackType.Default);
25+
26+
SetMetadata(data);
27+
28+
_originalObj = data;
29+
}
30+
31+
public void SetMetadata(JObject data)
32+
{
2533
Duration = Utils.GetValue<double>(data, "duration", 0);
2634

2735
Title = Utils.GetValue<string>(data, "title", null);
2836
Artist = Utils.GetValue<string>(data, "artist", null);
2937
Album = Utils.GetValue<string>(data, "album", null);
3038
Artwork = Utils.GetUri(data, "artwork", null);
3139

32-
_originalObj = data;
40+
if (_originalObj != null && _originalObj != data)
41+
_originalObj.Merge(data);
3342
}
3443

3544
public JObject ToObject()

windows/RNTrackPlayer/Players/Playback.cs

+8
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,14 @@ public void Remove(List<string> ids, IPromise promise)
120120
promise?.Resolve(null);
121121
}
122122

123+
public void UpdateTrack(int index, Track track)
124+
{
125+
queue[index] = track;
126+
127+
if (index == currentTrack)
128+
manager.GetMetadata().UpdateMetadata(track);
129+
}
130+
123131
public abstract SystemMediaTransportControls GetTransportControls();
124132

125133
protected abstract void Load(Track track, IPromise promise);

windows/RNTrackPlayer/RNTrackPlayer.nuget.props

+10-3
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
44
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
55
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
6-
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">C:\Android\react-native-track-player\windows\RNTrackPlayer\project.lock.json</ProjectAssetsFile>
6+
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">C:\Git\rn\rntp\windows\RNTrackPlayer\project.lock.json</ProjectAssetsFile>
77
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
8-
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\Guichaguri\.nuget\packages\</NuGetPackageFolders>
8+
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\g.oliveira\.nuget\packages\</NuGetPackageFolders>
99
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">ProjectJson</NuGetProjectStyle>
10-
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">4.8.1</NuGetToolVersion>
10+
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">4.9.3</NuGetToolVersion>
1111
</PropertyGroup>
1212
<PropertyGroup>
1313
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
@@ -18,4 +18,11 @@
1818
<Import Project="$(NuGetPackageRoot)microsoft.net.native.sharedlibrary-arm\1.7.0\build\Microsoft.Net.Native.SharedLibrary-arm.props" Condition="Exists('$(NuGetPackageRoot)microsoft.net.native.sharedlibrary-arm\1.7.0\build\Microsoft.Net.Native.SharedLibrary-arm.props')" />
1919
<Import Project="$(NuGetPackageRoot)microsoft.net.native.compiler\1.7.3\build\Microsoft.Net.Native.Compiler.props" Condition="Exists('$(NuGetPackageRoot)microsoft.net.native.compiler\1.7.3\build\Microsoft.Net.Native.Compiler.props')" />
2020
</ImportGroup>
21+
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
22+
<PkgMicrosoft_Net_Native_SharedLibrary-x86 Condition=" '$(PkgMicrosoft_Net_Native_SharedLibrary-x86)' == '' ">C:\Users\g.oliveira\.nuget\packages\microsoft.net.native.sharedlibrary-x86\1.7.0</PkgMicrosoft_Net_Native_SharedLibrary-x86>
23+
<PkgMicrosoft_Net_Native_SharedLibrary-x64 Condition=" '$(PkgMicrosoft_Net_Native_SharedLibrary-x64)' == '' ">C:\Users\g.oliveira\.nuget\packages\microsoft.net.native.sharedlibrary-x64\1.7.0</PkgMicrosoft_Net_Native_SharedLibrary-x64>
24+
<PkgMicrosoft_Net_Native_SharedLibrary-arm Condition=" '$(PkgMicrosoft_Net_Native_SharedLibrary-arm)' == '' ">C:\Users\g.oliveira\.nuget\packages\microsoft.net.native.sharedlibrary-arm\1.7.0</PkgMicrosoft_Net_Native_SharedLibrary-arm>
25+
<PkgMicrosoft_Net_Native_Compiler Condition=" '$(PkgMicrosoft_Net_Native_Compiler)' == '' ">C:\Users\g.oliveira\.nuget\packages\microsoft.net.native.compiler\1.7.3</PkgMicrosoft_Net_Native_Compiler>
26+
<PkgNewtonsoft_Json Condition=" '$(PkgNewtonsoft_Json)' == '' ">C:\Users\g.oliveira\.nuget\packages\newtonsoft.json\10.0.3</PkgNewtonsoft_Json>
27+
</PropertyGroup>
2128
</Project>

windows/RNTrackPlayer/TrackPlayerModule.cs

+22
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,28 @@ public void reset(IPromise promise)
130130
promise.Resolve(null);
131131
}
132132

133+
[ReactMethod]
134+
public void updateMetadataForTrack(string id, JObject metadata, IPromise promise)
135+
{
136+
var player = manager?.GetPlayer();
137+
if (Utils.CheckPlayback(player, promise)) return;
138+
139+
var queue = player.GetQueue();
140+
var index = queue.FindIndex(t => t.Id == id);
141+
142+
if (index == -1)
143+
{
144+
promise.Reject("track_not_in_queue", "Track not found");
145+
}
146+
else
147+
{
148+
var track = queue[index];
149+
track.SetMetadata(metadata);
150+
player.UpdateTrack(index, track);
151+
promise.Resolve(null);
152+
}
153+
}
154+
133155
[ReactMethod]
134156
public void removeUpcomingTracks(IPromise promise)
135157
{

0 commit comments

Comments
 (0)