【发布时间】:2021-11-26 22:42:46
【问题描述】:
我们正在使用libvlcsharp 在我们的xamarin.ios 应用程序中使用以下代码sn-p 播放实时mp3 网络流
public bool Play(Uri uri)
{
Media newMedia = new(this.LibVLC, uri);
Media? oldMedia = this.VLCMediaPlayer.Media;
bool success = this.VLCMediaPlayer.Play(newMedia);
oldMedia?.Dispose();
return success;
}
其中VLCMediaPlayer 是VLC 的MediaPlayer 类的一个实例。上面的代码可以正常工作,并且流媒体可以完美运行。
但是:
在某些时候我们需要切换流(即原始的实时 mp3 流继续/技术上没有结束,我们需要停止流式传输并切换到另一个流)。
就文档而言,似乎只需在不同的 URL 上调用 Play()应该就可以了。问题是,它没有。原始流停止几毫秒,然后继续。
我们的代码如下所示:
// use our custom Play() wrapper
Play(new Uri("https://whatever.com/some-live-stream.mp3"));
// ... do other stuff
// at some time later switch to a different stream using the same Play() wrapper
Play(new Uri("https://whatever.com/file-stream.mp3"));
问题:
VLC 不会开始播放 file-stream.mp3,而是挂起几毫秒,然后继续播放 some-live-stream.mp3。为什么会这样?我们该如何解决?
更新(似乎是一个集成错误):
运行带有调试输出的 libVLC 揭示了这一点:
[00000177e9c21830] main input debug: Creating an input for 'file-stream.mp3'
[00000177e9c21830]playing uri
main input debug: using timeshift granularity of 50 MiB
[00000177e9c21830] main input debug: using timeshift path: C:\Users\EXPUNGED\AppData\Local\Temp
[00000177e9c21830] main input debug: `http://127.0.0.1:5050/file-stream.mp3' gives access `http' demux `any' path `127.0.0.1:5050/file-stream.mp3'
[00000177e9b29350] main input source debug: creating demux: access='http' demux='any' location='127.0.0.1:5050/file-stream.mp3' file='\\127.0.0.1:5050\file-stream.mp3'
[00000177e92e1d60] main demux debug: looking for access_demux module matching "http": 15 candidates
[00000177e92e1d60] main demux debug: no access_demux modules matched
[00000177e904e6d0] main stream debug: creating access: http://127.0.0.1:5050/file-stream.mp3
[00000177e904e6d0] main stream debug: (path: \\127.0.0.1:5050\file-stream.mp3)
[00000177e904e6d0] main stream debug: looking for access module matching "http": 27 candidates
[00000177e904e6d0] http stream debug: resolving 127.0.0.1 ...
[00000177e904e6d0] http stream debug: outgoing request:
GET /file-stream.mp3 HTTP/1.1
Host: 127.0.0.1:5050
Accept: */*
Accept-Language: en_US
User-Agent: VLC/3.0.16 LibVLC/3.0.16
Range: bytes=0-
[00000177e904e6d0] http stream debug: connection failed
[00000177e904e6d0] access stream error: HTTP connection failure
[00000177e904e6d0] http stream debug: querying proxy for http://127.0.0.1:5050/file-stream.mp3
[00000177e904e6d0] http stream debug: no proxy
[00000177e904e6d0] http stream debug: http: server='127.0.0.1' port=5050 file='/file-stream.mp3'
[00000177e904e6d0] main stream debug: net: connecting to 127.0.0.1 port 5050
[00000177e904e6d0] http stream error: cannot connect to 127.0.0.1:5050
[00000177e904e6d0] main stream debug: no access modules matched
(上面的 sn-p 从第二个 Play() 被调用时开始。)
立即引起注意的是HTTP connection failure。
但是,让我扩展一下我们最小的可重现示例。在生产中,我们不仅需要切换流一次,而且需要多次切换。我们停止了第一个 some-live-stream.mp3 流(它运行 24/7 实时服务器端)。然后我们需要切换到file-stream.mp3,这是一个大约 10 秒长的 mp3 文件。播放完文件后,我们将继续播放第一个流(some-live-stream.mp3)。
因此,除了最初包含在此问题中的 Play() 方法之外,我们还编写了自定义 Enqueue() 方法。
因此,我们内部 VLC 包装类的整个相关代码实际上如下所示:
public sealed class MediaService
{
private readonly LibVLC _libVLC;
private readonly ConcurrentQueue<Uri> _playlist = new();
public MediaPlayer VLCPlayer { get; }
internal MediaService(LibVLC libVLC)
{
_libVLC = libVLC;
VLCPlayer = new MediaPlayer(_libVLC);
VLCPlayer.EndReached += VLCPlayer_EndReached;
}
public bool Play(Uri uri)
{
Media newMedia = new(_libVLC, uri);
Media? oldMedia = VLCPlayer.Media;
bool success = VLCPlayer.Play(newMedia);
oldMedia?.Dispose();
CurrentUrl = uri.AbsoluteUri;
return success;
}
public bool IsStartingOrPlaying() =>
VLCPlayer.State is VLCState.Buffering
or VLCState.Opening
or VLCState.Playing;
public void Enqueue(Uri uri)
{
if (IsStartingOrPlaying())
{
_playlist.Enqueue(uri);
}
else
{
Play(uri);
}
}
private void VLCPlayer_EndReached(object sender, EventArgs e)
{
if (_playlist.TryDequeue(out Uri? result))
{
// don't deadlock on VLC callback
Task.Run(() => Play(result));
}
}
}
现在到最小的可重现示例:
以下代码失败(即导致 HTTP 连接失败)
using our.vlc.wrapper;
CoreLoader.Initialize(true);
MediaService mediaService = MediaServiceFactory.GetSharedInstance();
// start streaming the live stream
Task.Run(() => mediaService.Play("http://127.0.0.1:5050/some-live-stream.mp3"))
// do other stuff...
.ContinueWith((_) => Thread.Sleep(10000))
// now interrupt the original stream with the mp3 file
.ContinueWith((_) => mediaService.Play("http://127.0.0.1:5050/file-stream.mp3"))
// and continue the live stream after mp3 file stream ends
.ContinueWith((_) => mediaService.Enqueue("http://127.0.0.1:5050/some-live-stream.mp3"));
Console.ReadLine();
尝试流式传输 http://127.0.0.1:5050/file-stream.mp3 时的 HTTP 异常显然会导致原始问题中描述的“滞后”,然后原始流按预期继续。
但是 此代码 sn-p 有效:
using our.vlc.wrapper;
CoreLoader.Initialize(true);
MediaService mediaService = MediaServiceFactory.GetSharedInstance();
// start streaming the live stream
Task.Run(() => mediaService.Play("http://127.0.0.1:5050/some-live-stream.mp3"))
// do other stuff...
.ContinueWith((_) => Thread.Sleep(10000))
// now interrupt the original stream with the mp3 file
.ContinueWith((_) => mediaService.Play("http://127.0.0.1:5050/file-stream.mp3"))
// another sleep seems to cause no connection failure
.ContinueWith((_) => Thread.Sleep(10000))
// call PLAY() instead of ENQUEUE()
.ContinueWith((_) => mediaService.Play("http://127.0.0.1:5050/some-live-stream.mp3"));
Console.ReadLine();
所以实际上我们的Enqueue() 方法似乎是罪魁祸首。但是,当/file-stream.mp3 端点经过良好测试甚至在第二个示例中有效时,我不明白为什么它的实现会成为问题并导致看似随机 HTTP 连接失败。
使用某种排队方式对我们的项目至关重要,因此我们不能像我们在最小可重现示例中所做的那样,退回到 Thread.Sleep() 调用。我们如何修复我们的 Enqueue() 方法,为什么它没有按预期工作?
更新 2
使用 Wireshark 在“HTTP 连接失败”期间检查流量显示:
我们可以看到与第一个/some-live-stream.mp3的终止TCP连接的FIN、FIN-ACK、ACK与对@的HTTP请求的SYN、SYN-ACK、ACK混合在一起987654355@ 所以它有点难以阅读,但很明显实际的 HTTP GET never 被发送,所以/file-stream.mp3 的端点永远不会被调用。记住以下几点会更容易理解:
端口5050 是提供流的服务器。端口20118 是具有第一个some-live-stream 连接的VLC。端口20224 是VLC,与file-stream.mp3 的第二个连接失败,在底部您可以看到来自端口20225 的连接正在初始化,这是some-live-stream 的延续。
由于某种原因,VLC 立即终止新建立的/file-stream.mp3 请求的 TCP 连接,一旦建立(查找来自端口 20124->5050 的请求)。所以 VLC 主动终止连接 :C.
然后在最后几行中,原来的/some-live-stream.mp3 连接被重新建立。
那么为什么 VLC 甚至无法为 /file-stream.mp3 请求发送 HTTP GET 呢?
【问题讨论】:
-
确实应该。请发布一个最小的项目来重现您的问题
-
也共享 libvlc 详细日志
-
@cube45 我相应地更新了问题并提供了更多上下文,因为该错误似乎不在最初发布的代码中。
-
当我们要求一个最小的复制时,它在 Main 的行数的意义上不是最小的,但在涉及的复杂性方面是最小的。您将问题定位到 Enqueue 方法,但没有进一步简化。
-
@cube45 在对原始问题进行过度简化之后,我包含了来自我们的
MediaService类的所有相关代码,这些代码将被执行以确保:) 所以是的,你是对的它可以进一步简化,但足以查明确切的问题。感谢您抽出宝贵的时间:)
标签: libvlcsharp xamarin.ios c# .net-core streaming libvlc libvlcsharp