support static trailer streaming

This commit is contained in:
Luke Pulverenti 2013-05-17 14:05:49 -04:00
parent da7af24fca
commit e2d6a5c05d
10 changed files with 195 additions and 22 deletions

View File

@ -56,6 +56,8 @@
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.XML" /> <Reference Include="System.XML" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -82,6 +84,7 @@
<Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" /> <Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" />
<Compile Include="Playback\BaseStreamingService.cs" /> <Compile Include="Playback\BaseStreamingService.cs" />
<Compile Include="Playback\Progressive\ProgressiveStreamWriter.cs" /> <Compile Include="Playback\Progressive\ProgressiveStreamWriter.cs" />
<Compile Include="Playback\StaticRemoteStreamWriter.cs" />
<Compile Include="Playback\StreamRequest.cs" /> <Compile Include="Playback\StreamRequest.cs" />
<Compile Include="Playback\StreamState.cs" /> <Compile Include="Playback\StreamState.cs" />
<Compile Include="Playback\Progressive\VideoService.cs" /> <Compile Include="Playback\Progressive\VideoService.cs" />

View File

@ -621,10 +621,27 @@ namespace MediaBrowser.Api.Playback
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
protected string GetUserAgentParam(BaseItem item) protected string GetUserAgentParam(BaseItem item)
{
var useragent = GetUserAgent(item);
if (!string.IsNullOrEmpty(useragent))
{
return "-user-agent \"" + useragent + "\"";
}
return string.Empty;
}
/// <summary>
/// Gets the user agent.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
protected string GetUserAgent(BaseItem item)
{ {
if (item.Path.IndexOf("apple.com", StringComparison.OrdinalIgnoreCase) != -1) if (item.Path.IndexOf("apple.com", StringComparison.OrdinalIgnoreCase) != -1)
{ {
return "-user-agent \"QuickTime/7.6.2\""; return "QuickTime/7.6.2";
} }
return string.Empty; return string.Empty;

View File

@ -1,4 +1,7 @@
using MediaBrowser.Api.Images; using System.Net;
using System.Net.Cache;
using System.Net.Http;
using MediaBrowser.Api.Images;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo; using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@ -188,6 +191,11 @@ namespace MediaBrowser.Api.Playback.Progressive
var responseHeaders = new Dictionary<string, string>(); var responseHeaders = new Dictionary<string, string>();
if (request.Static && state.Item.LocationType == LocationType.Remote)
{
return GetStaticRemoteStreamResult(state.Item, responseHeaders, isHeadRequest).Result;
}
var outputPath = GetOutputFilePath(state); var outputPath = GetOutputFilePath(state);
var outputPathExists = File.Exists(outputPath); var outputPathExists = File.Exists(outputPath);
@ -209,6 +217,60 @@ namespace MediaBrowser.Api.Playback.Progressive
return GetStreamResult(state, responseHeaders, isHeadRequest).Result; return GetStreamResult(state, responseHeaders, isHeadRequest).Result;
} }
/// <summary>
/// Gets the static remote stream result.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="responseHeaders">The response headers.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <returns>Task{System.Object}.</returns>
private async Task<object> GetStaticRemoteStreamResult(BaseItem item, Dictionary<string, string> responseHeaders, bool isHeadRequest)
{
responseHeaders["Accept-Ranges"] = "none";
var httpClient = new HttpClient(new WebRequestHandler
{
CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache),
AutomaticDecompression = DecompressionMethods.None
});
using (var message = new HttpRequestMessage(HttpMethod.Get, item.Path))
{
var useragent = GetUserAgent(item);
if (!string.IsNullOrEmpty(useragent))
{
message.Headers.Add("User-Agent", useragent);
}
var response = await httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var contentType = response.Content.Headers.ContentType.MediaType;
// Headers only
if (isHeadRequest)
{
response.Dispose();
return ResultFactory.GetResult(null, contentType, responseHeaders);
}
var result = new StaticRemoteStreamWriter(response, httpClient);
result.Options["Content-Type"] = contentType;
// Add the response headers to the result object
foreach (var header in responseHeaders)
{
result.Options[header.Key] = header.Value;
}
return result;
}
}
/// <summary> /// <summary>
/// Gets the album art response. /// Gets the album art response.
/// </summary> /// </summary>

View File

@ -0,0 +1,75 @@
using ServiceStack.Service;
using ServiceStack.ServiceHost;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback
{
/// <summary>
/// Class StaticRemoteStreamWriter
/// </summary>
public class StaticRemoteStreamWriter : IStreamWriter, IHasOptions
{
/// <summary>
/// The _input stream
/// </summary>
private readonly HttpResponseMessage _msg;
private readonly HttpClient _client;
/// <summary>
/// The _options
/// </summary>
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
/// <summary>
/// Initializes a new instance of the <see cref="StaticRemoteStreamWriter"/> class.
/// </summary>
public StaticRemoteStreamWriter(HttpResponseMessage msg, HttpClient client)
{
_msg = msg;
_client = client;
}
/// <summary>
/// Gets the options.
/// </summary>
/// <value>The options.</value>
public IDictionary<string, string> Options
{
get { return _options; }
}
/// <summary>
/// Writes to.
/// </summary>
/// <param name="responseStream">The response stream.</param>
public void WriteTo(Stream responseStream)
{
var task = WriteToAsync(responseStream);
Task.WaitAll(task);
}
/// <summary>
/// Writes to async.
/// </summary>
/// <param name="responseStream">The response stream.</param>
/// <returns>Task.</returns>
public async Task WriteToAsync(Stream responseStream)
{
using (_client)
{
using (_msg)
{
using (var input = await _msg.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
await input.CopyToAsync(responseStream).ConfigureAwait(false);
}
}
}
}
}
}

View File

@ -135,7 +135,7 @@ namespace MediaBrowser.Api.UserLibrary
{ {
if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater)) if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater))
{ {
items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.Name, StringComparison.OrdinalIgnoreCase) < 1); items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.Name, StringComparison.CurrentCultureIgnoreCase) < 1);
} }
var filters = request.GetFilters().ToList(); var filters = request.GetFilters().ToList();

View File

@ -456,7 +456,7 @@ namespace MediaBrowser.Api.UserLibrary
if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater)) if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater))
{ {
items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.OrdinalIgnoreCase) < 1); items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1);
} }
// Filter by Series Status // Filter by Series Status

View File

@ -97,7 +97,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}; };
client = new HttpClient(handler); client = new HttpClient(handler);
client.Timeout = TimeSpan.FromSeconds(30); client.Timeout = TimeSpan.FromSeconds(15);
_httpClients.TryAdd(host, client); _httpClients.TryAdd(host, client);
} }

View File

@ -63,8 +63,15 @@ namespace MediaBrowser.Controller.Providers.TV
} }
case "Airs_Time": case "Airs_Time":
item.AirTime = reader.ReadElementContentAsString(); {
break; var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
item.AirTime = val;
}
break;
}
case "SeriesName": case "SeriesName":
item.Name = reader.ReadElementContentAsString(); item.Name = reader.ReadElementContentAsString();

View File

@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Session
/// <param name="deviceId">The device id.</param> /// <param name="deviceId">The device id.</param>
/// <param name="deviceName">Name of the device.</param> /// <param name="deviceName">Name of the device.</param>
/// <exception cref="System.ArgumentNullException"></exception> /// <exception cref="System.ArgumentNullException"></exception>
void OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName); Task OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName);
/// <summary> /// <summary>
/// Used to report playback progress for an item /// Used to report playback progress for an item

View File

@ -200,7 +200,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="deviceName">Name of the device.</param> /// <param name="deviceName">Name of the device.</param>
/// <exception cref="System.ArgumentNullException"> /// <exception cref="System.ArgumentNullException">
/// </exception> /// </exception>
public void OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName) public async Task OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName)
{ {
if (user == null) if (user == null)
{ {
@ -213,6 +213,15 @@ namespace MediaBrowser.Server.Implementations.Session
UpdateNowPlayingItemId(user, clientType, deviceId, deviceName, item, false); UpdateNowPlayingItemId(user, clientType, deviceId, deviceName, item, false);
var key = item.GetUserDataKey();
var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false);
data.PlayCount++;
data.LastPlayedDate = DateTime.UtcNow;
await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
// Nothing to save here // Nothing to save here
// Fire events to inform plugins // Fire events to inform plugins
EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
@ -254,7 +263,7 @@ namespace MediaBrowser.Server.Implementations.Session
{ {
var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false); var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false);
UpdatePlayState(item, data, positionTicks.Value, false); UpdatePlayState(item, data, positionTicks.Value);
await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false); await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
} }
@ -297,7 +306,7 @@ namespace MediaBrowser.Server.Implementations.Session
if (positionTicks.HasValue) if (positionTicks.HasValue)
{ {
UpdatePlayState(item, data, positionTicks.Value, true); UpdatePlayState(item, data, positionTicks.Value);
} }
else else
{ {
@ -322,11 +331,12 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="item">The item</param> /// <param name="item">The item</param>
/// <param name="data">User data for the item</param> /// <param name="data">User data for the item</param>
/// <param name="positionTicks">The current playback position</param> /// <param name="positionTicks">The current playback position</param>
/// <param name="incrementPlayCount">Whether or not to increment playcount</param> private void UpdatePlayState(BaseItem item, UserItemData data, long positionTicks)
private void UpdatePlayState(BaseItem item, UserItemData data, long positionTicks, bool incrementPlayCount)
{ {
var hasRuntime = item.RunTimeTicks.HasValue && item.RunTimeTicks > 0;
// If a position has been reported, and if we know the duration // If a position has been reported, and if we know the duration
if (positionTicks > 0 && item.RunTimeTicks.HasValue && item.RunTimeTicks > 0) if (positionTicks > 0 && hasRuntime)
{ {
var pctIn = Decimal.Divide(positionTicks, item.RunTimeTicks.Value) * 100; var pctIn = Decimal.Divide(positionTicks, item.RunTimeTicks.Value) * 100;
@ -334,7 +344,6 @@ namespace MediaBrowser.Server.Implementations.Session
if (pctIn < _configurationManager.Configuration.MinResumePct) if (pctIn < _configurationManager.Configuration.MinResumePct)
{ {
positionTicks = 0; positionTicks = 0;
incrementPlayCount = false;
} }
// If we're at the end, assume completed // If we're at the end, assume completed
@ -356,19 +365,19 @@ namespace MediaBrowser.Server.Implementations.Session
} }
} }
} }
else if (!hasRuntime)
{
// If we don't know the runtime we'll just have to assume it was fully played
data.Played = true;
positionTicks = 0;
}
if (item is Audio) if (item is Audio)
{ {
data.PlaybackPositionTicks = 0; positionTicks = 0;
} }
data.PlaybackPositionTicks = positionTicks; data.PlaybackPositionTicks = positionTicks;
if (incrementPlayCount)
{
data.PlayCount++;
data.LastPlayedDate = DateTime.UtcNow;
}
} }
} }
} }