需要身份验证的视频流如何播放?

3
我有一个与REST服务通信的Windows Store应用程序(C# / XAML),需要在某些时刻播放该服务提供的视频流。
如果我只是将流URI分配给“MediaElement.Source”属性,它不起作用,因为请求需要进行身份验证。我需要自定义由“MediaElement”控件发送的请求,以添加cookie,凭据和一些其他自定义标头,但我找不到任何方法或属性来执行此操作。
我该如何做?是否可能实现这个功能?

请查看MediaElement.SetMediaStreamSource。您可能需要使用HTTP客户端进行身份验证并单独创建流(也可以在Nuget中查看MSFT HTTP库),然后将MediaElement的源设置为该流。 - Nate Diamond
@NateDiamond,谢谢,但我没有这个方法...根据文档,它在Windows 8.0中可用,但它需要一个IMediaSource参数,这只在8.1中可用。我怀疑该方法存在于本地的MediaElement控件中,但在.NET API中没有显示。 - Thomas Levesque
啊,你说得对!不过,SetSource 方法可以接受 RandomAccessStream - Nate Diamond
@NateDiamond,不幸的是这对我来说不是一个选项,我真的需要流式传输数据。我正在研究一种使用HTTP Range头寻找给定位置的自定义实现。 - Thomas Levesque
1
@NateDiamond,我搞定了,我会尽快发布我的解决方案。 - Thomas Levesque
显示剩余4条评论
1个回答

7

好的,我明白了。基本上,解决方案有两个部分:

  • 手动进行HTTP请求(使用任何必需的凭据或标头)
  • 将响应流包装在自定义的IRandomAccessStream中,该流通过向服务器发出另一个请求来实现Seek,并使用Range标头指定我需要的流的哪个部分。

这是RandomAccessStream实现:

delegate Task<Stream> AsyncRangeDownloader(ulong start, ulong? end);

class StreamingRandomAccessStream : IRandomAccessStream
{
    private readonly AsyncRangeDownloader _downloader;
    private readonly ulong _size;

    public StreamingRandomAccessStream(Stream startStream, AsyncRangeDownloader downloader, ulong size)
    {
        if (startStream != null)
            _stream = startStream.AsInputStream();
        _downloader = downloader;
        _size = size;
    }

    private IInputStream _stream;
    private ulong _requestedPosition;

    public void Dispose()
    {
        if (_stream != null)
            _stream.Dispose();
    }

    public IAsyncOperationWithProgress<IBuffer, uint> ReadAsync(IBuffer buffer, uint count, InputStreamOptions options)
    {
        return AsyncInfo.Run<IBuffer, uint>(async (cancellationToken, progress) =>
        {
            progress.Report(0);
            if (_stream == null)
            {
                var netStream = await _downloader(_requestedPosition, null);
                _stream = netStream.AsInputStream();
            }
            var result = await _stream.ReadAsync(buffer, count, options).AsTask(cancellationToken, progress);
            return result;
        });
    }

    public void Seek(ulong position)
    {
        if (_stream != null)
            _stream.Dispose();
        _requestedPosition = position;
        _stream = null;
    }

    public bool CanRead { get { return true; } }
    public bool CanWrite { get { return false; } }
    public ulong Size { get { return _size; } set { throw new NotSupportedException(); } }

    public IAsyncOperationWithProgress<uint, uint> WriteAsync(IBuffer buffer) { throw new NotSupportedException(); }
    public IAsyncOperation<bool> FlushAsync() { throw new NotSupportedException(); }
    public IInputStream GetInputStreamAt(ulong position) { throw new NotSupportedException(); }
    public IOutputStream GetOutputStreamAt(ulong position) { throw new NotSupportedException(); }
    public IRandomAccessStream CloneStream() { throw new NotSupportedException(); }
    public ulong Position { get { throw new NotSupportedException(); } }
}

以下是使用方法:

private HttpClient _client;
private void InitClient()
{
    _client = new HttpClient();
    // Configure the client as needed with CookieContainer, Credentials, etc
    // ...
}

private async Task StartVideoStreamingAsync(Uri uri)
{
    var request = new HttpRequestMessage(HttpMethod.Get, uri);
    // Add required headers
    // ...

    var response = await _client.SendAsync(request);
    ulong length = (ulong)response.Content.Headers.ContentLength;
    string mimeType = response.Content.Headers.ContentType.MediaType;
    Stream responseStream = await response.Content.ReadAsStreamAsync();

    // Delegate that will fetch a stream for the specified range
    AsyncRangeDownloader downloader = async (start, end) =>
        {
            var request2 = new HttpRequestMessage();
            request2.Headers.Range = new RangeHeaderValue((long?)start, (long?)end);
            // Add other required headers
            // ...
            var response2 = await _client.SendAsync(request2);
            return await response2.Content.ReadAsStreamAsync();
        };

    var videoStream = new StreamingRandomAccessStream(responseStream, downloader, length);
    _mediaElement.SetSource(videoStream, mimeType);
}

用户可以在视频中任意位置进行搜索,流媒体将发出另一个请求以获取指定位置的流。这仍然比我想象的要复杂,但它能够工作...请注意,服务器必须支持请求中的“Range”头,并且必须在初始响应中发出“Content-Length”头。

1
如果您需要一个类似于 Windows.Web.Http.HttpClient 的等效解决方案,请尝试使用 HttpRandomAccessStream:https://github.com/kiewic/MediaElementWithHttpClient - kiewic
@kiewic,不错!但是能够传递HttpRequestMessage比仅仅传递客户端和URI更加灵活。 - Thomas Levesque
我考虑过那个选项,但在 Windows.Web.Http 中,HttpRequestMessage 不可重用,因此每次位置更改时都需要一个新实例。此外,可以在 HttpClient 中配置自定义标头、身份验证、重定向、压缩等。 - kiewic
@kiewic太厉害了!!!!回答了我的问题并且拿到了悬赏 https://dev59.com/-lsW5IYBdhLWcg3wM0yF - Stamos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接