|
@@ -1,3 +1,5 @@
|
|
|
+#nullable disable
|
|
|
+
|
|
|
#pragma warning disable CS1591
|
|
|
|
|
|
using System;
|
|
@@ -9,7 +11,6 @@ using System.Threading.Tasks;
|
|
|
using Emby.Dlna.Didl;
|
|
|
using Jellyfin.Data.Entities;
|
|
|
using Jellyfin.Data.Events;
|
|
|
-using MediaBrowser.Common.Configuration;
|
|
|
using MediaBrowser.Controller.Dlna;
|
|
|
using MediaBrowser.Controller.Drawing;
|
|
|
using MediaBrowser.Controller.Entities;
|
|
@@ -41,7 +42,6 @@ namespace Emby.Dlna.PlayTo
|
|
|
private readonly IUserDataManager _userDataManager;
|
|
|
private readonly ILocalizationManager _localization;
|
|
|
private readonly IMediaSourceManager _mediaSourceManager;
|
|
|
- private readonly IConfigurationManager _config;
|
|
|
private readonly IMediaEncoder _mediaEncoder;
|
|
|
|
|
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
|
@@ -68,7 +68,6 @@ namespace Emby.Dlna.PlayTo
|
|
|
IUserDataManager userDataManager,
|
|
|
ILocalizationManager localization,
|
|
|
IMediaSourceManager mediaSourceManager,
|
|
|
- IConfigurationManager config,
|
|
|
IMediaEncoder mediaEncoder)
|
|
|
{
|
|
|
_session = session;
|
|
@@ -84,7 +83,6 @@ namespace Emby.Dlna.PlayTo
|
|
|
_userDataManager = userDataManager;
|
|
|
_localization = localization;
|
|
|
_mediaSourceManager = mediaSourceManager;
|
|
|
- _config = config;
|
|
|
_mediaEncoder = mediaEncoder;
|
|
|
}
|
|
|
|
|
@@ -106,6 +104,22 @@ namespace Emby.Dlna.PlayTo
|
|
|
_deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft;
|
|
|
}
|
|
|
|
|
|
+ /*
|
|
|
+ * Send a message to the DLNA device to notify what is the next track in the playlist.
|
|
|
+ */
|
|
|
+ private async Task SendNextTrackMessage(int currentPlayListItemIndex, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ if (currentPlayListItemIndex >= 0 && currentPlayListItemIndex < _playlist.Count - 1)
|
|
|
+ {
|
|
|
+ // The current playing item is indeed in the play list and we are not yet at the end of the playlist.
|
|
|
+ var nextItemIndex = currentPlayListItemIndex + 1;
|
|
|
+ var nextItem = _playlist[nextItemIndex];
|
|
|
+
|
|
|
+ // Send the SetNextAvTransport message.
|
|
|
+ await _device.SetNextAvTransport(nextItem.StreamUrl, GetDlnaHeaders(nextItem), nextItem.Didl, cancellationToken).ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private void OnDeviceUnavailable()
|
|
|
{
|
|
|
try
|
|
@@ -136,7 +150,7 @@ namespace Emby.Dlna.PlayTo
|
|
|
|
|
|
private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e)
|
|
|
{
|
|
|
- if (_disposed)
|
|
|
+ if (_disposed || string.IsNullOrEmpty(e.OldMediaInfo.Url))
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
@@ -160,6 +174,15 @@ namespace Emby.Dlna.PlayTo
|
|
|
var newItemProgress = GetProgressInfo(streamInfo);
|
|
|
|
|
|
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
|
|
|
+
|
|
|
+ // Send a message to the DLNA device to notify what is the next track in the playlist.
|
|
|
+ var currentItemIndex = _playlist.FindIndex(item => item.StreamInfo.ItemId == streamInfo.ItemId);
|
|
|
+ if (currentItemIndex >= 0)
|
|
|
+ {
|
|
|
+ _currentPlaylistIndex = currentItemIndex;
|
|
|
+ }
|
|
|
+
|
|
|
+ await SendNextTrackMessage(currentItemIndex, CancellationToken.None);
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
@@ -326,7 +349,7 @@ namespace Emby.Dlna.PlayTo
|
|
|
|
|
|
public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
|
|
|
{
|
|
|
- _logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
|
|
|
+ _logger.LogDebug("{0} - Received PlayRequest: {1}", _session.DeviceName, command.PlayCommand);
|
|
|
|
|
|
var user = command.ControllingUserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(command.ControllingUserId);
|
|
|
|
|
@@ -337,25 +360,26 @@ namespace Emby.Dlna.PlayTo
|
|
|
}
|
|
|
|
|
|
var startIndex = command.StartIndex ?? 0;
|
|
|
+ int len = items.Count - startIndex;
|
|
|
if (startIndex > 0)
|
|
|
{
|
|
|
- items = items.Skip(startIndex).ToList();
|
|
|
+ items = items.GetRange(startIndex, len);
|
|
|
}
|
|
|
|
|
|
- var playlist = new List<PlaylistItem>();
|
|
|
- var isFirst = true;
|
|
|
+ var playlist = new PlaylistItem[len];
|
|
|
|
|
|
- foreach (var item in items)
|
|
|
+ // Not nullable enabled - so this is required.
|
|
|
+ playlist[0] = CreatePlaylistItem(
|
|
|
+ items[0],
|
|
|
+ user,
|
|
|
+ command.StartPositionTicks ?? 0,
|
|
|
+ command.MediaSourceId ?? string.Empty,
|
|
|
+ command.AudioStreamIndex,
|
|
|
+ command.SubtitleStreamIndex);
|
|
|
+
|
|
|
+ for (int i = 1; i < len; i++)
|
|
|
{
|
|
|
- if (isFirst && command.StartPositionTicks.HasValue)
|
|
|
- {
|
|
|
- playlist.Add(CreatePlaylistItem(item, user, command.StartPositionTicks.Value, command.MediaSourceId, command.AudioStreamIndex, command.SubtitleStreamIndex));
|
|
|
- isFirst = false;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- playlist.Add(CreatePlaylistItem(item, user, 0, null, null, null));
|
|
|
- }
|
|
|
+ playlist[i] = CreatePlaylistItem(items[i], user, 0, string.Empty, null, null);
|
|
|
}
|
|
|
|
|
|
_logger.LogDebug("{0} - Playlist created", _session.DeviceName);
|
|
@@ -428,6 +452,11 @@ namespace Emby.Dlna.PlayTo
|
|
|
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex);
|
|
|
|
|
|
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
|
|
|
+
|
|
|
+ // Send a message to the DLNA device to notify what is the next track in the play list.
|
|
|
+ var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
|
|
|
+ await SendNextTrackMessage(newItemIndex, CancellationToken.None);
|
|
|
+
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -468,8 +497,8 @@ namespace Emby.Dlna.PlayTo
|
|
|
_dlnaManager.GetDefaultProfile();
|
|
|
|
|
|
var mediaSources = item is IHasMediaSources
|
|
|
- ? _mediaSourceManager.GetStaticMediaSources(item, true, user)
|
|
|
- : new List<MediaSourceInfo>();
|
|
|
+ ? _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray()
|
|
|
+ : Array.Empty<MediaSourceInfo>();
|
|
|
|
|
|
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
|
|
|
playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
|
|
@@ -502,8 +531,8 @@ namespace Emby.Dlna.PlayTo
|
|
|
|
|
|
if (streamInfo.MediaType == DlnaProfileType.Audio)
|
|
|
{
|
|
|
- return new ContentFeatureBuilder(profile)
|
|
|
- .BuildAudioHeader(
|
|
|
+ return ContentFeatureBuilder.BuildAudioHeader(
|
|
|
+ profile,
|
|
|
streamInfo.Container,
|
|
|
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
|
|
streamInfo.TargetAudioBitrate,
|
|
@@ -517,8 +546,8 @@ namespace Emby.Dlna.PlayTo
|
|
|
|
|
|
if (streamInfo.MediaType == DlnaProfileType.Video)
|
|
|
{
|
|
|
- var list = new ContentFeatureBuilder(profile)
|
|
|
- .BuildVideoHeader(
|
|
|
+ var list = ContentFeatureBuilder.BuildVideoHeader(
|
|
|
+ profile,
|
|
|
streamInfo.Container,
|
|
|
streamInfo.TargetVideoCodec.FirstOrDefault(),
|
|
|
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
|
@@ -548,7 +577,7 @@ namespace Emby.Dlna.PlayTo
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaSourceInfo> mediaSources, DeviceProfile profile, string deviceId, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
|
|
|
+ private PlaylistItem GetPlaylistItem(BaseItem item, MediaSourceInfo[] mediaSources, DeviceProfile profile, string deviceId, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
|
|
|
{
|
|
|
if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
@@ -557,7 +586,7 @@ namespace Emby.Dlna.PlayTo
|
|
|
StreamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildVideoItem(new VideoOptions
|
|
|
{
|
|
|
ItemId = item.Id,
|
|
|
- MediaSources = mediaSources.ToArray(),
|
|
|
+ MediaSources = mediaSources,
|
|
|
Profile = profile,
|
|
|
DeviceId = deviceId,
|
|
|
MaxBitrate = profile.MaxStreamingBitrate,
|
|
@@ -577,7 +606,7 @@ namespace Emby.Dlna.PlayTo
|
|
|
StreamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildAudioItem(new AudioOptions
|
|
|
{
|
|
|
ItemId = item.Id,
|
|
|
- MediaSources = mediaSources.ToArray(),
|
|
|
+ MediaSources = mediaSources,
|
|
|
Profile = profile,
|
|
|
DeviceId = deviceId,
|
|
|
MaxBitrate = profile.MaxStreamingBitrate,
|
|
@@ -590,7 +619,7 @@ namespace Emby.Dlna.PlayTo
|
|
|
|
|
|
if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
- return new PlaylistItemFactory().Create((Photo)item, profile);
|
|
|
+ return PlaylistItemFactory.Create((Photo)item, profile);
|
|
|
}
|
|
|
|
|
|
throw new ArgumentException("Unrecognized item type.");
|
|
@@ -626,6 +655,9 @@ namespace Emby.Dlna.PlayTo
|
|
|
|
|
|
await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
|
+ // Send a message to the DLNA device to notify what is the next track in the play list.
|
|
|
+ await SendNextTrackMessage(index, cancellationToken);
|
|
|
+
|
|
|
var streamInfo = currentitem.StreamInfo;
|
|
|
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
|
|
|
{
|
|
@@ -669,62 +701,57 @@ namespace Emby.Dlna.PlayTo
|
|
|
|
|
|
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
|
|
{
|
|
|
- if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType))
|
|
|
- {
|
|
|
- switch (commandType)
|
|
|
- {
|
|
|
- case GeneralCommandType.VolumeDown:
|
|
|
- return _device.VolumeDown(cancellationToken);
|
|
|
- case GeneralCommandType.VolumeUp:
|
|
|
- return _device.VolumeUp(cancellationToken);
|
|
|
- case GeneralCommandType.Mute:
|
|
|
- return _device.Mute(cancellationToken);
|
|
|
- case GeneralCommandType.Unmute:
|
|
|
- return _device.Unmute(cancellationToken);
|
|
|
- case GeneralCommandType.ToggleMute:
|
|
|
- return _device.ToggleMute(cancellationToken);
|
|
|
- case GeneralCommandType.SetAudioStreamIndex:
|
|
|
- if (command.Arguments.TryGetValue("Index", out string index))
|
|
|
+ switch (command.Name)
|
|
|
+ {
|
|
|
+ case GeneralCommandType.VolumeDown:
|
|
|
+ return _device.VolumeDown(cancellationToken);
|
|
|
+ case GeneralCommandType.VolumeUp:
|
|
|
+ return _device.VolumeUp(cancellationToken);
|
|
|
+ case GeneralCommandType.Mute:
|
|
|
+ return _device.Mute(cancellationToken);
|
|
|
+ case GeneralCommandType.Unmute:
|
|
|
+ return _device.Unmute(cancellationToken);
|
|
|
+ case GeneralCommandType.ToggleMute:
|
|
|
+ return _device.ToggleMute(cancellationToken);
|
|
|
+ case GeneralCommandType.SetAudioStreamIndex:
|
|
|
+ if (command.Arguments.TryGetValue("Index", out string index))
|
|
|
+ {
|
|
|
+ if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
|
|
|
{
|
|
|
- if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
|
|
|
- {
|
|
|
- return SetAudioStreamIndex(val);
|
|
|
- }
|
|
|
-
|
|
|
- throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
|
|
|
+ return SetAudioStreamIndex(val);
|
|
|
}
|
|
|
|
|
|
- throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
|
|
|
- case GeneralCommandType.SetSubtitleStreamIndex:
|
|
|
- if (command.Arguments.TryGetValue("Index", out index))
|
|
|
- {
|
|
|
- if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
|
|
|
- {
|
|
|
- return SetSubtitleStreamIndex(val);
|
|
|
- }
|
|
|
+ throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
|
|
|
+ }
|
|
|
|
|
|
- throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
|
|
|
+ throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
|
|
|
+ case GeneralCommandType.SetSubtitleStreamIndex:
|
|
|
+ if (command.Arguments.TryGetValue("Index", out index))
|
|
|
+ {
|
|
|
+ if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
|
|
|
+ {
|
|
|
+ return SetSubtitleStreamIndex(val);
|
|
|
}
|
|
|
|
|
|
- throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
|
|
|
- case GeneralCommandType.SetVolume:
|
|
|
- if (command.Arguments.TryGetValue("Volume", out string vol))
|
|
|
- {
|
|
|
- if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume))
|
|
|
- {
|
|
|
- return _device.SetVolume(volume, cancellationToken);
|
|
|
- }
|
|
|
+ throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
|
|
|
+ }
|
|
|
|
|
|
- throw new ArgumentException("Unsupported volume value supplied.");
|
|
|
+ throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
|
|
|
+ case GeneralCommandType.SetVolume:
|
|
|
+ if (command.Arguments.TryGetValue("Volume", out string vol))
|
|
|
+ {
|
|
|
+ if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume))
|
|
|
+ {
|
|
|
+ return _device.SetVolume(volume, cancellationToken);
|
|
|
}
|
|
|
|
|
|
- throw new ArgumentException("Volume argument cannot be null");
|
|
|
- default:
|
|
|
- return Task.CompletedTask;
|
|
|
- }
|
|
|
- }
|
|
|
+ throw new ArgumentException("Unsupported volume value supplied.");
|
|
|
+ }
|
|
|
|
|
|
- return Task.CompletedTask;
|
|
|
+ throw new ArgumentException("Volume argument cannot be null");
|
|
|
+ default:
|
|
|
+ return Task.CompletedTask;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private async Task SetAudioStreamIndex(int? newIndex)
|
|
@@ -744,6 +771,10 @@ namespace Emby.Dlna.PlayTo
|
|
|
|
|
|
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
|
|
|
|
|
|
+ // Send a message to the DLNA device to notify what is the next track in the play list.
|
|
|
+ var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
|
|
|
+ await SendNextTrackMessage(newItemIndex, CancellationToken.None);
|
|
|
+
|
|
|
if (EnableClientSideSeek(newItem.StreamInfo))
|
|
|
{
|
|
|
await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
|
|
@@ -769,6 +800,10 @@ namespace Emby.Dlna.PlayTo
|
|
|
|
|
|
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
|
|
|
|
|
|
+ // Send a message to the DLNA device to notify what is the next track in the play list.
|
|
|
+ var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
|
|
|
+ await SendNextTrackMessage(newItemIndex, CancellationToken.None);
|
|
|
+
|
|
|
if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0)
|
|
|
{
|
|
|
await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
|
|
@@ -779,13 +814,14 @@ namespace Emby.Dlna.PlayTo
|
|
|
|
|
|
private async Task SeekAfterTransportChange(long positionTicks, CancellationToken cancellationToken)
|
|
|
{
|
|
|
- const int maxWait = 15000000;
|
|
|
- const int interval = 500;
|
|
|
+ const int MaxWait = 15000000;
|
|
|
+ const int Interval = 500;
|
|
|
+
|
|
|
var currentWait = 0;
|
|
|
- while (_device.TransportState != TransportState.Playing && currentWait < maxWait)
|
|
|
+ while (_device.TransportState != TransportState.Playing && currentWait < MaxWait)
|
|
|
{
|
|
|
- await Task.Delay(interval).ConfigureAwait(false);
|
|
|
- currentWait += interval;
|
|
|
+ await Task.Delay(Interval, cancellationToken).ConfigureAwait(false);
|
|
|
+ currentWait += Interval;
|
|
|
}
|
|
|
|
|
|
await _device.Seek(TimeSpan.FromTicks(positionTicks), cancellationToken).ConfigureAwait(false);
|
|
@@ -816,7 +852,7 @@ namespace Emby.Dlna.PlayTo
|
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
- public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
|
|
|
+ public Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken)
|
|
|
{
|
|
|
if (_disposed)
|
|
|
{
|
|
@@ -828,17 +864,17 @@ namespace Emby.Dlna.PlayTo
|
|
|
return Task.CompletedTask;
|
|
|
}
|
|
|
|
|
|
- if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase))
|
|
|
+ if (name == SessionMessageType.Play)
|
|
|
{
|
|
|
return SendPlayCommand(data as PlayRequest, cancellationToken);
|
|
|
}
|
|
|
|
|
|
- if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
|
|
|
+ if (name == SessionMessageType.Playstate)
|
|
|
{
|
|
|
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
|
|
|
}
|
|
|
|
|
|
- if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
|
|
|
+ if (name == SessionMessageType.GeneralCommand)
|
|
|
{
|
|
|
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
|
|
|
}
|
|
@@ -886,7 +922,10 @@ namespace Emby.Dlna.PlayTo
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
|
|
|
+ if (_mediaSourceManager != null)
|
|
|
+ {
|
|
|
+ mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
|
|
|
+ }
|
|
|
|
|
|
return mediaSource;
|
|
|
}
|
|
@@ -900,16 +939,16 @@ namespace Emby.Dlna.PlayTo
|
|
|
|
|
|
var parts = url.Split('/');
|
|
|
|
|
|
- for (var i = 0; i < parts.Length; i++)
|
|
|
+ for (var i = 0; i < parts.Length - 1; i++)
|
|
|
{
|
|
|
var part = parts[i];
|
|
|
|
|
|
if (string.Equals(part, "audio", StringComparison.OrdinalIgnoreCase) ||
|
|
|
string.Equals(part, "videos", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
- if (parts.Length > i + 1)
|
|
|
+ if (Guid.TryParse(parts[i + 1], out var result))
|
|
|
{
|
|
|
- return Guid.Parse(parts[i + 1]);
|
|
|
+ return result;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -948,7 +987,6 @@ namespace Emby.Dlna.PlayTo
|
|
|
request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
|
|
|
request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
|
|
|
request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
|
|
|
-
|
|
|
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
|
|
|
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
|
|
|
request.StartPositionTicks = GetLongValue(values, "StartPositionTicks");
|