@ -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 . Volume Down :
return _device . VolumeDown ( cancellationToken ) ;
case GeneralCommandType . VolumeUp :
return _device . VolumeUp ( cancellationToken ) ;
case GeneralCommandType . M ute:
return _device . M ute( cancellationToken ) ;
case GeneralCommandType . Unm ute:
return _device . Unm ute( 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 . Volume Up :
return _device . VolumeUp ( cancellationToken ) ;
case GeneralCommandType . Mute :
return _device . Mute ( cancellationToken ) ;
case GeneralCommandType . Unm ute:
return _device . Unm ute( cancellationToken ) ;
case GeneralCommandType . ToggleM ute:
return _device . ToggleM ute( 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 < m axWait)
while ( _device . TransportState ! = TransportState . Playing & & currentWait < M axWait)
{
await Task . Delay ( interval ) . ConfigureAwait ( false ) ;
currentWait + = i nterval;
await Task . Delay ( Interval, cancellationToken ) . ConfigureAwait ( false ) ;
currentWait + = I nterval;
}
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" ) ;