Skip to content

Commit de01b33

Browse files
authored
Merge pull request #192 from tobexyz/feat/issue187
feat: issue #187 selecting only media resources and try to choose alw…
2 parents fdb100f + 049d83d commit de01b33

2 files changed

Lines changed: 158 additions & 6 deletions

File tree

yaacc/src/main/java/de/yaacc/player/AVTransportPlayer.java

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,29 @@
3333
import org.fourthline.cling.model.meta.Icon;
3434
import org.fourthline.cling.model.meta.RemoteDevice;
3535
import org.fourthline.cling.model.meta.Service;
36+
import org.fourthline.cling.model.types.UDAServiceType;
3637
import de.yaacc.upnp.callback.avtransport.GetPositionInfo;
3738
import de.yaacc.upnp.callback.avtransport.GetTransportInfo;
3839
import de.yaacc.upnp.callback.avtransport.Pause;
3940
import de.yaacc.upnp.callback.avtransport.Play;
4041
import de.yaacc.upnp.callback.avtransport.Seek;
4142
import de.yaacc.upnp.callback.avtransport.SetAVTransportURI;
4243
import de.yaacc.upnp.callback.avtransport.Stop;
44+
import de.yaacc.upnp.callback.connectionmanager.GetProtocolInfo;
4345
import org.fourthline.cling.support.contentdirectory.DIDLParser;
4446
import org.fourthline.cling.support.model.DIDLContent;
4547
import org.fourthline.cling.support.model.DIDLObject;
4648
import org.fourthline.cling.support.model.PositionInfo;
49+
import org.fourthline.cling.support.model.ProtocolInfo;
50+
import org.fourthline.cling.support.model.ProtocolInfos;
4751
import org.fourthline.cling.support.model.Res;
4852
import org.fourthline.cling.support.model.TransportInfo;
4953
import org.fourthline.cling.support.model.TransportState;
5054
import org.fourthline.cling.support.model.item.Item;
5155

56+
import java.util.concurrent.CountDownLatch;
57+
import java.util.concurrent.TimeUnit;
58+
5259
import java.net.URI;
5360
import java.net.URL;
5461
import java.text.SimpleDateFormat;
@@ -182,11 +189,15 @@ protected Object loadItem(PlayableItem playableItem) {
182189
protected void startItem(PlayableItem playableItem, Object loadedItem) {
183190
if (playableItem == null || getDevice() == null)
184191
return;
185-
YaaccLogger.d(getClass().getName(), "Uri: " + playableItem.getUri());
186-
YaaccLogger.d(getClass().getName(), "Duration: " + playableItem.getDuration());
192+
193+
// Try to select best resource for this device
194+
PlayableItem deviceOptimizedItem = selectBestResourceForDevice(playableItem);
195+
196+
YaaccLogger.d(getClass().getName(), "Uri: " + deviceOptimizedItem.getUri());
197+
YaaccLogger.d(getClass().getName(), "Duration: " + deviceOptimizedItem.getDuration());
187198
YaaccLogger.d(getClass().getName(),
188-
"MimeType: " + playableItem.getMimeType());
189-
YaaccLogger.d(getClass().getName(), "Title: " + playableItem.getTitle());
199+
"MimeType: " + deviceOptimizedItem.getMimeType());
200+
YaaccLogger.d(getClass().getName(), "Title: " + deviceOptimizedItem.getTitle());
190201
Service<?, ?> service = getUpnpClient().getAVTransportService(getDevice());
191202
if (service == null) {
192203
YaaccLogger.d(getClass().getName(),
@@ -196,7 +207,103 @@ protected void startItem(PlayableItem playableItem, Object loadedItem) {
196207
}
197208

198209
// Check transport state first and handle accordingly
199-
checkTransportStateForStart(playableItem, service);
210+
checkTransportStateForStart(deviceOptimizedItem, service);
211+
}
212+
213+
/**
214+
* Select the best resource for this specific device based on supported protocols
215+
*/
216+
private PlayableItem selectBestResourceForDevice(PlayableItem playableItem) {
217+
Item item = playableItem.getItem();
218+
if (item == null || item.getResources().isEmpty()) {
219+
return playableItem;
220+
}
221+
222+
// Get device's supported protocols
223+
Service<?, ?> cmService = getDevice().findService(new UDAServiceType("ConnectionManager"));
224+
if (cmService == null) {
225+
YaaccLogger.d(getClass().getName(), "No ConnectionManager service, using default resource");
226+
return playableItem;
227+
}
228+
229+
// Query supported protocols synchronously
230+
final ProtocolInfos[] supportedProtocols = new ProtocolInfos[1];
231+
final CountDownLatch latch = new CountDownLatch(1);
232+
233+
executorService.execute(
234+
new GetProtocolInfo(cmService, getHttpRequestSender()) {
235+
@Override
236+
public void received(ActionInvocation actionInvocation, ProtocolInfos sinkProtocolInfos, ProtocolInfos sourceProtocolInfos) {
237+
supportedProtocols[0] = sinkProtocolInfos;
238+
latch.countDown();
239+
}
240+
241+
@Override
242+
public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
243+
YaaccLogger.d(AVTransportPlayer.class.getName(), "GetProtocolInfo failed: " + defaultMsg);
244+
latch.countDown();
245+
}
246+
}
247+
);
248+
249+
try {
250+
latch.await(2, TimeUnit.SECONDS);
251+
} catch (InterruptedException e) {
252+
YaaccLogger.d(getClass().getName(), "GetProtocolInfo timeout");
253+
return playableItem;
254+
}
255+
256+
if (supportedProtocols[0] == null || supportedProtocols[0].isEmpty()) {
257+
YaaccLogger.d(getClass().getName(), "No supported protocols found, using default resource");
258+
return playableItem;
259+
}
260+
261+
// Find best matching resource
262+
Res bestMatch = null;
263+
long bestBitrate = 0;
264+
265+
for (Res resource : item.getResources()) {
266+
if (resource.getProtocolInfo() == null) continue;
267+
268+
String contentFormat = resource.getProtocolInfo().getContentFormat();
269+
if (contentFormat == null || contentFormat.isEmpty()) continue;
270+
271+
// Check if device supports this format
272+
boolean supported = false;
273+
for (ProtocolInfo deviceProtocol : supportedProtocols[0]) {
274+
if (deviceProtocol.getContentFormat().equals(contentFormat) ||
275+
deviceProtocol.getContentFormat().equals("*") ||
276+
deviceProtocol.getContentFormat().startsWith(contentFormat.split("/")[0] + "/*")) {
277+
supported = true;
278+
break;
279+
}
280+
}
281+
282+
if (!supported) {
283+
YaaccLogger.d(getClass().getName(), "Device doesn't support: " + contentFormat);
284+
continue;
285+
}
286+
287+
// Among supported formats, prefer higher bitrate
288+
Long bitrate = resource.getBitrate();
289+
if (bitrate != null && bitrate > bestBitrate) {
290+
bestBitrate = bitrate;
291+
bestMatch = resource;
292+
} else if (bestMatch == null) {
293+
bestMatch = resource;
294+
}
295+
}
296+
297+
if (bestMatch != null && !bestMatch.equals(item.getFirstResource())) {
298+
YaaccLogger.d(getClass().getName(), "Selected device-optimized resource: " +
299+
bestMatch.getProtocolInfo().getContentFormat() + " bitrate: " + bestMatch.getBitrate());
300+
// Create new PlayableItem with selected resource
301+
Item optimizedItem = new Item(item);
302+
optimizedItem.setResources(java.util.Collections.singletonList(bestMatch));
303+
return new PlayableItem(optimizedItem, (int) playableItem.getDuration());
304+
}
305+
306+
return playableItem;
200307
}
201308

202309
private void checkTransportStateForStart(PlayableItem playableItem, Service<?, ?> service) {

yaacc/src/main/java/de/yaacc/player/PlayableItem.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public PlayableItem(Item item, int defaultDuration) {
4646
this.item = item;
4747
id = UUID.randomUUID();
4848
setTitle(item.getTitle());
49-
Res resource = item.getFirstResource();
49+
Res resource = selectBestResource(item);
5050
if (resource != null) {
5151
setUri(Uri.parse(resource.getValue()));
5252
String mimeType = resource.getProtocolInfo().getContentFormat();
@@ -100,6 +100,51 @@ public PlayableItem() {
100100
id = UUID.randomUUID();
101101
}
102102

103+
/**
104+
* Select the best playable resource from an item.
105+
* Filters out non-media types and prefers higher quality.
106+
*/
107+
private Res selectBestResource(Item item) {
108+
if (item.getResources() == null || item.getResources().isEmpty()) {
109+
return null;
110+
}
111+
112+
Res bestResource = null;
113+
long bestBitrate = 0;
114+
115+
for (Res resource : item.getResources()) {
116+
String contentFormat = resource.getProtocolInfo().getContentFormat();
117+
if (contentFormat == null || contentFormat.isEmpty()) {
118+
continue;
119+
}
120+
121+
// Only accept audio, video, or image types
122+
if (!contentFormat.startsWith("audio/") &&
123+
!contentFormat.startsWith("video/") &&
124+
!contentFormat.startsWith("image/")) {
125+
YaaccLogger.d(getClass().getName(), "Skipping non-media resource: " + contentFormat);
126+
continue;
127+
}
128+
129+
// Prefer higher bitrate
130+
Long bitrate = resource.getBitrate();
131+
if (bitrate != null && bitrate > bestBitrate) {
132+
bestBitrate = bitrate;
133+
bestResource = resource;
134+
} else if (bestResource == null) {
135+
bestResource = resource;
136+
}
137+
}
138+
139+
if (bestResource != null) {
140+
YaaccLogger.d(getClass().getName(), "Selected resource: " +
141+
bestResource.getProtocolInfo().getContentFormat() +
142+
" bitrate: " + bestResource.getBitrate());
143+
}
144+
145+
return bestResource;
146+
}
147+
103148

104149
/**
105150
* @return the mimeType

0 commit comments

Comments
 (0)