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

View File

@ -621,10 +621,27 @@ namespace MediaBrowser.Api.Playback
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
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)
{
return "-user-agent \"QuickTime/7.6.2\"";
return "QuickTime/7.6.2";
}
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.MediaInfo;
using MediaBrowser.Common.Net;
@ -188,6 +191,11 @@ namespace MediaBrowser.Api.Playback.Progressive
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 outputPathExists = File.Exists(outputPath);
@ -209,6 +217,60 @@ namespace MediaBrowser.Api.Playback.Progressive
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>
/// Gets the album art response.
/// </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))
{
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();

View File

@ -456,7 +456,7 @@ namespace MediaBrowser.Api.UserLibrary
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

View File

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

View File

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

View File

@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Session
/// <param name="deviceId">The device id.</param>
/// <param name="deviceName">Name of the device.</param>
/// <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>
/// 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>
/// <exception cref="System.ArgumentNullException">
/// </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)
{
@ -213,6 +213,15 @@ namespace MediaBrowser.Server.Implementations.Session
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
// Fire events to inform plugins
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);
UpdatePlayState(item, data, positionTicks.Value, false);
UpdatePlayState(item, data, positionTicks.Value);
await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
}
@ -297,7 +306,7 @@ namespace MediaBrowser.Server.Implementations.Session
if (positionTicks.HasValue)
{
UpdatePlayState(item, data, positionTicks.Value, true);
UpdatePlayState(item, data, positionTicks.Value);
}
else
{
@ -322,11 +331,12 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="item">The item</param>
/// <param name="data">User data for the item</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, bool incrementPlayCount)
private void UpdatePlayState(BaseItem item, UserItemData data, long positionTicks)
{
var hasRuntime = item.RunTimeTicks.HasValue && item.RunTimeTicks > 0;
// 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;
@ -334,7 +344,6 @@ namespace MediaBrowser.Server.Implementations.Session
if (pctIn < _configurationManager.Configuration.MinResumePct)
{
positionTicks = 0;
incrementPlayCount = false;
}
// 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)
{
data.PlaybackPositionTicks = 0;
positionTicks = 0;
}
data.PlaybackPositionTicks = positionTicks;
if (incrementPlayCount)
{
data.PlayCount++;
data.LastPlayedDate = DateTime.UtcNow;
}
}
}
}