support theme songs in the web client

This commit is contained in:
Luke Pulverenti 2014-04-14 23:54:52 -04:00
parent 2913638b67
commit e89d4e605b
14 changed files with 189 additions and 47 deletions

View File

@ -187,7 +187,7 @@ namespace MediaBrowser.Api.Playback
if (!state.HasMediaStreams) if (!state.HasMediaStreams)
{ {
return state.IsInputVideo ? "-map -0:s" : string.Empty; return state.IsInputVideo ? "-sn" : string.Empty;
} }
if (state.VideoStream != null) if (state.VideoStream != null)
@ -1493,7 +1493,7 @@ namespace MediaBrowser.Api.Playback
if (videoRequest != null) if (videoRequest != null)
{ {
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream, state.VideoType)) if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
{ {
videoRequest.VideoCodec = "copy"; videoRequest.VideoCodec = "copy";
} }
@ -1507,19 +1507,13 @@ namespace MediaBrowser.Api.Playback
return state; return state;
} }
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream, VideoType videoType) private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
{ {
if (videoStream.IsInterlaced) if (videoStream.IsInterlaced)
{ {
return false; return false;
} }
// Not going to attempt this with folder rips
if (videoType != VideoType.VideoFile)
{
return false;
}
// Source and target codecs must match // Source and target codecs must match
if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase)) if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{ {

View File

@ -24,7 +24,8 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary> /// </summary>
public abstract class BaseHlsService : BaseStreamingService public abstract class BaseHlsService : BaseStreamingService
{ {
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager) protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager)
: base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager)
{ {
} }
@ -77,6 +78,7 @@ namespace MediaBrowser.Api.Playback.Hls
return ProcessRequestAsync(request).Result; return ProcessRequestAsync(request).Result;
} }
private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1);
/// <summary> /// <summary>
/// Processes the request async. /// Processes the request async.
/// </summary> /// </summary>
@ -103,31 +105,40 @@ namespace MediaBrowser.Api.Playback.Hls
} }
var playlist = GetOutputFilePath(state); var playlist = GetOutputFilePath(state);
var isPlaylistNewlyCreated = false;
// If the playlist doesn't already exist, startup ffmpeg if (File.Exists(playlist))
if (!File.Exists(playlist))
{
isPlaylistNewlyCreated = true;
try
{
await StartFfMpeg(state, playlist).ConfigureAwait(false);
}
catch
{
state.Dispose();
throw;
}
}
else
{ {
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
} }
else
if (isPlaylistNewlyCreated)
{ {
await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false); await FfmpegStartLock.WaitAsync().ConfigureAwait(false);
try
{
if (File.Exists(playlist))
{
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
}
else
{
// If the playlist doesn't already exist, startup ffmpeg
try
{
await StartFfMpeg(state, playlist).ConfigureAwait(false);
}
catch
{
state.Dispose();
throw;
}
}
await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false);
}
finally
{
FfmpegStartLock.Release();
}
} }
int audioBitrate; int audioBitrate;
@ -295,7 +306,7 @@ namespace MediaBrowser.Api.Playback.Hls
// If performSubtitleConversions is true we're actually starting ffmpeg // If performSubtitleConversions is true we're actually starting ffmpeg
var startNumberParam = performSubtitleConversions ? GetStartNumber(state).ToString(UsCulture) : "0"; var startNumberParam = performSubtitleConversions ? GetStartNumber(state).ToString(UsCulture) : "0";
var args = string.Format("{0} {1} -i {2}{3} -map_metadata -1 -threads {4} {5} {6} -sc_threshold 0 {7} -hls_time {8} -start_number {9} -hls_list_size {10} \"{11}\"", var args = string.Format("{0} {1} -i {2}{3} -map_metadata -1 -threads {4} {5} {6} -sc_threshold 0 {7} -hls_time {8} -start_number {9} -hls_list_size {10} \"{11}\"",
itsOffset, itsOffset,
inputModifier, inputModifier,

View File

@ -210,6 +210,9 @@ namespace MediaBrowser.Dlna.PlayTo
return SetVolume(tmp); return SetVolume(tmp);
} }
/// <summary>
/// Sets volume on a scale of 0-100
/// </summary>
public async Task<bool> SetVolume(int value) public async Task<bool> SetVolume(int value)
{ {
var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume"); var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.Net; using System.Globalization;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -206,7 +207,8 @@ namespace MediaBrowser.Dlna.PlayTo
IsPaused = _device.IsPaused, IsPaused = _device.IsPaused,
MediaSourceId = playlistItem.MediaSourceId, MediaSourceId = playlistItem.MediaSourceId,
AudioStreamIndex = playlistItem.AudioStreamIndex, AudioStreamIndex = playlistItem.AudioStreamIndex,
SubtitleStreamIndex = playlistItem.SubtitleStreamIndex SubtitleStreamIndex = playlistItem.SubtitleStreamIndex,
VolumeLevel = _device.Volume
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
@ -614,6 +616,8 @@ namespace MediaBrowser.Dlna.PlayTo
} }
} }
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
{ {
GeneralCommandType commandType; GeneralCommandType commandType;
@ -632,6 +636,24 @@ namespace MediaBrowser.Dlna.PlayTo
return _device.VolumeUp(true); return _device.VolumeUp(true);
case GeneralCommandType.ToggleMute: case GeneralCommandType.ToggleMute:
return _device.ToggleMute(); return _device.ToggleMute();
case GeneralCommandType.SetVolume:
{
string volumeArg;
if (command.Arguments.TryGetValue("Volume", out volumeArg))
{
int volume;
if (int.TryParse(volumeArg, NumberStyles.Any, _usCulture, out volume))
{
return _device.SetVolume(volume);
}
throw new ArgumentException("Unsupported volume value supplied.");
}
throw new ArgumentException("Volume argument cannot be null");
}
default: default:
return Task.FromResult(true); return Task.FromResult(true);
} }

View File

@ -265,7 +265,8 @@ namespace MediaBrowser.Dlna.PlayTo
GeneralCommandType.VolumeUp.ToString(), GeneralCommandType.VolumeUp.ToString(),
GeneralCommandType.Mute.ToString(), GeneralCommandType.Mute.ToString(),
GeneralCommandType.Unmute.ToString(), GeneralCommandType.Unmute.ToString(),
GeneralCommandType.ToggleMute.ToString() GeneralCommandType.ToggleMute.ToString(),
GeneralCommandType.SetVolume.ToString()
} }
}); });

View File

@ -57,8 +57,17 @@ namespace MediaBrowser.Dlna.Server
{ {
builder.Append("<UDN>" + SecurityElement.Escape(_serverUdn) + "</UDN>"); builder.Append("<UDN>" + SecurityElement.Escape(_serverUdn) + "</UDN>");
builder.Append("<dlna:X_DLNACAP>" + SecurityElement.Escape(_profile.XDlnaCap ?? string.Empty) + "</dlna:X_DLNACAP>"); builder.Append("<dlna:X_DLNACAP>" + SecurityElement.Escape(_profile.XDlnaCap ?? string.Empty) + "</dlna:X_DLNACAP>");
builder.Append("<dlna:X_DLNADOC>M-DMS-1.50</dlna:X_DLNADOC>");
builder.Append("<dlna:X_DLNADOC>" + SecurityElement.Escape(_profile.XDlnaDoc ?? string.Empty) + "</dlna:X_DLNADOC>"); if (!string.IsNullOrWhiteSpace(_profile.XDlnaDoc))
{
builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" +
SecurityElement.Escape(_profile.XDlnaDoc) + "</dlna:X_DLNADOC>");
}
else
{
builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">DMS-1.50</dlna:X_DLNADOC>");
}
builder.Append("<friendlyName>" + SecurityElement.Escape(_profile.FriendlyName ?? string.Empty) + "</friendlyName>"); builder.Append("<friendlyName>" + SecurityElement.Escape(_profile.FriendlyName ?? string.Empty) + "</friendlyName>");
builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>"); builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>");
builder.Append("<manufacturer>" + SecurityElement.Escape(_profile.Manufacturer ?? string.Empty) + "</manufacturer>"); builder.Append("<manufacturer>" + SecurityElement.Escape(_profile.Manufacturer ?? string.Empty) + "</manufacturer>");
@ -99,7 +108,7 @@ namespace MediaBrowser.Dlna.Server
foreach (var service in GetServices()) foreach (var service in GetServices())
{ {
builder.Append("<icon>"); builder.Append("<service>");
builder.Append("<serviceType>" + SecurityElement.Escape(service.ServiceType ?? string.Empty) + "</serviceType>"); builder.Append("<serviceType>" + SecurityElement.Escape(service.ServiceType ?? string.Empty) + "</serviceType>");
builder.Append("<serviceId>" + SecurityElement.Escape(service.ServiceId ?? string.Empty) + "</serviceId>"); builder.Append("<serviceId>" + SecurityElement.Escape(service.ServiceId ?? string.Empty) + "</serviceId>");
@ -107,7 +116,7 @@ namespace MediaBrowser.Dlna.Server
builder.Append("<controlURL>" + SecurityElement.Escape(service.ControlUrl ?? string.Empty) + "</controlURL>"); builder.Append("<controlURL>" + SecurityElement.Escape(service.ControlUrl ?? string.Empty) + "</controlURL>");
builder.Append("<eventSubURL>" + SecurityElement.Escape(service.EventSubUrl ?? string.Empty) + "</eventSubURL>"); builder.Append("<eventSubURL>" + SecurityElement.Escape(service.EventSubUrl ?? string.Empty) + "</eventSubURL>");
builder.Append("</icon>"); builder.Append("</service>");
} }
builder.Append("</serviceList>"); builder.Append("</serviceList>");

View File

@ -147,7 +147,7 @@ namespace MediaBrowser.Dlna.Server
foreach (var d in Devices) foreach (var d in Devices)
{ {
if (!string.IsNullOrEmpty(req) && req != d.Type) if (!string.IsNullOrEmpty(req) && !string.Equals(req, d.Type, StringComparison.OrdinalIgnoreCase))
{ {
continue; continue;
} }
@ -263,13 +263,19 @@ namespace MediaBrowser.Dlna.Server
} }
} }
foreach (var t in new[] { "upnp:rootdevice", "urn:schemas-upnp-org:device:MediaServer:1", "urn:schemas-upnp-org:service:ContentDirectory:1", "uuid:" + uuid }) foreach (var t in new[]
{
"upnp:rootdevice",
"urn:schemas-upnp-org:device:MediaServer:1",
"urn:schemas-upnp-org:service:ContentDirectory:1",
"uuid:" + uuid
})
{ {
list.Add(new UpnpDevice(uuid, t, descriptor, address)); list.Add(new UpnpDevice(uuid, t, descriptor, address));
} }
NotifyAll(); NotifyAll();
_logger.Debug("Registered mount {0}", uuid); _logger.Debug("Registered mount {0} at {1}", uuid, descriptor);
} }
private void UnregisterNotification(Guid uuid) private void UnregisterNotification(Guid uuid)

View File

@ -1,7 +1,7 @@
using System.ComponentModel; using MediaBrowser.Model.Configuration;
using System.Diagnostics;
using MediaBrowser.Model.Configuration;
using System; using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace MediaBrowser.Model.Dto namespace MediaBrowser.Model.Dto

View File

@ -51,6 +51,12 @@ namespace MediaBrowser.Model.Session
/// <value>The user id.</value> /// <value>The user id.</value>
public string UserId { get; set; } public string UserId { get; set; }
/// <summary>
/// Gets or sets the user primary image tag.
/// </summary>
/// <value>The user primary image tag.</value>
public Guid? UserPrimaryImageTag { get; set; }
/// <summary> /// <summary>
/// Gets or sets the name of the user. /// Gets or sets the name of the user.
/// </summary> /// </summary>

View File

@ -34,7 +34,10 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
{ {
var collectionType = args.GetCollectionType(); var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)) var isStandalone = args.Parent == null;
if (isStandalone ||
string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase))
{ {
return new Controller.Entities.Audio.Audio(); return new Controller.Entities.Audio.Audio();
} }

View File

@ -450,5 +450,70 @@
"LabelMinResumeDuration": "Min resume duration (seconds):", "LabelMinResumeDuration": "Min resume duration (seconds):",
"LabelMinResumePercentageHelp": "Titles are assumed unplayed if stopped before this time", "LabelMinResumePercentageHelp": "Titles are assumed unplayed if stopped before this time",
"LabelMaxResumePercentageHelp": "Titles are assumed fully played if stopped after this time", "LabelMaxResumePercentageHelp": "Titles are assumed fully played if stopped after this time",
"LabelMinResumeDurationHelp": "Titles shorter than this will not be resumable" "LabelMinResumeDurationHelp": "Titles shorter than this will not be resumable",
"TitleAutoOrganize": "Auto-Organize",
"TabActivityLog": "Activity Log",
"HeaderName": "Name",
"HeaderDate": "Date",
"HeaderSource": "Source",
"HeaderStatus": "Status",
"HeaderDestination": "Destination",
"HeaderProgram": "Program",
"HeaderClients": "Clients",
"LabelCompleted": "Completed",
"LabelFailed": "Failed",
"LabelSkipped": "Skipped",
"HeaderEpisodeOrganization": "Episode Organization",
"LabelSeries": "Series:",
"LabelSeasonNumber": "Season number:",
"LabelEpisodeNumber": "Episode number:",
"LabelEndingEpisodeNumber": "Ending episode number:",
"LabelEndingEpisodeNumberHelp": "Only required for multi-episode files",
"HeaderSupportTheTeam": "Support the Media Browser Team",
"LabelSupportAmount": "Amount (USD)",
"HeaderSupportTheTeamHelp": "Help ensure the continued development of this project by donating. A portion of all donations will be contributed to other free tools we depend on.",
"ButtonEnterSupporterKey": "Enter supporter key",
"DonationNextStep": "Once complete, please return and enter your supporter key, which you will receive by email.",
"AutoOrganizeHelp": "Auto-organize monitors your download folders for new files and moves them to your media directories.",
"AutoOrganizeTvHelp": "TV file organizing will only add episodes to existing series. It will not create new series folders.",
"OptionEnableEpisodeOrganization": "Enable new episode organization",
"LabelWatchFolder": "Watch folder:",
"LabelWatchFolderHelp": "The server will poll this folder during the 'Organize new media files' scheduled task.",
"ButtonViewScheduledTasks": "View scheduled tasks",
"LabelMinFileSizeForOrganize": "Minimum file size (MB):",
"LabelMinFileSizeForOrganizeHelp": "Files under this size will be ignored.",
"LabelSeasonFolderPattern": "Season folder pattern:",
"LabelSeasonZeroFolderName": "Season zero folder name:",
"HeaderEpisodeFilePattern": "Episode file pattern",
"LabelEpisodePattern": "Episode pattern:",
"LabelMultiEpisodePattern": "Multi-Episode pattern:",
"HeaderSupportedPatterns": "Supported Patterns",
"HeaderTerm": "Term",
"HeaderPattern": "Pattern",
"HeaderResult": "Result",
"LabelDeleteEmptyFolders": "Delete empty folders after organizing",
"LabelDeleteEmptyFoldersHelp": "Enable this to keep the download directory clean.",
"LabelDeleteLeftOverFiles": "Delete left over files with the following extensions:",
"LabelDeleteLeftOverFilesHelp": "Separate with ;. For example: .nfo;.txt",
"OptionOverwriteExistingEpisodes": "Overwrite existing episodes",
"LabelTransferMethod": "Transfer method",
"OptionCopy": "Copy",
"OptionMove": "Move",
"LabelTransferMethodHelp": "Copy or move files from the watch folder",
"HeaderLatestNews": "Latest News",
"HeaderHelpImproveMediaBrowser": "Help Improve Media Browser",
"HeaderRunningTasks": "Running Tasks",
"HeaderActiveDevices": "Active Devices",
"HeaderPendingInstallations": "Pending Installations",
"HeaerServerInformation": "Server Information",
"ButtonRestartNow": "Restart Now",
"ButtonRestart": "Restart",
"ButtonShutdown": "Shutdown",
"ButtonUpdateNow": "Update Now",
"PleaseUpdateManually": "Please shutdown the server and update manually.",
"NewServerVersionAvailable": "A new version of Media Browser Server is available!",
"ServerUpToDate": "Media Browser Server is up to date",
"ErrorConnectingToMediaBrowserRepository": "There was an error connecting to the remote Media Browser repository.",
"LabelComponentsUpdated": "The following components have been installed or updated:",
"MessagePleaseRestartServerToFinishUpdating": "Please restart the server to finish applying updates."
} }

View File

@ -1182,6 +1182,13 @@ namespace MediaBrowser.Server.Implementations.Session
if (session.UserId.HasValue) if (session.UserId.HasValue)
{ {
dto.UserId = session.UserId.Value.ToString("N"); dto.UserId = session.UserId.Value.ToString("N");
var user = _userManager.GetUserById(session.UserId.Value);
if (user != null)
{
dto.UserPrimaryImageTag = GetImageCacheTag(user, ImageType.Primary);
}
} }
return dto; return dto;
@ -1311,6 +1318,11 @@ namespace MediaBrowser.Server.Implementations.Session
} }
} }
if (backropItem == null)
{
backropItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Backdrop));
}
if (thumbItem == null) if (thumbItem == null)
{ {
thumbItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Thumb)); thumbItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Thumb));
@ -1322,7 +1334,7 @@ namespace MediaBrowser.Server.Implementations.Session
info.ThumbItemId = GetDtoId(thumbItem); info.ThumbItemId = GetDtoId(thumbItem);
} }
if (thumbItem != null) if (backropItem != null)
{ {
info.BackdropImageTag = GetImageCacheTag(backropItem, ImageType.Backdrop); info.BackdropImageTag = GetImageCacheTag(backropItem, ImageType.Backdrop);
info.BackdropItemId = GetDtoId(backropItem); info.BackdropItemId = GetDtoId(backropItem);

View File

@ -608,6 +608,7 @@ namespace MediaBrowser.WebDashboard.Api
"supporterkeypage.js", "supporterkeypage.js",
"supporterpage.js", "supporterpage.js",
"episodes.js", "episodes.js",
"thememediaplayer.js",
"tvgenres.js", "tvgenres.js",
"tvlatest.js", "tvlatest.js",
"tvpeople.js", "tvpeople.js",

View File

@ -220,6 +220,9 @@
<Content Include="dashboard-ui\css\images\media\tvflyout.png"> <Content Include="dashboard-ui\css\images\media\tvflyout.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\css\images\nowplayingdefault.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\livetv.css"> <Content Include="dashboard-ui\css\livetv.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -505,6 +508,9 @@
<Content Include="dashboard-ui\livetvrecordings.html"> <Content Include="dashboard-ui\livetvrecordings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\nowplaying.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\advancedpaths.js"> <Content Include="dashboard-ui\scripts\advancedpaths.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -637,6 +643,9 @@
<Content Include="dashboard-ui\scripts\livetvsuggested.js"> <Content Include="dashboard-ui\scripts\livetvsuggested.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\scripts\thememediaplayer.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\tvupcoming.js"> <Content Include="dashboard-ui\scripts\tvupcoming.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>