I am using libVLCSharp to play audio and video media in a Winforms app. I am able to fetch embedded cover art from MP3 files by calling this PlayMediaFile() method with a FileInfo pointing to an MP3 with artwork:`
// media & artwork must be disposed after playing media
async Task PlayMediaFile(FileInfo mediaFI) {
var media = new Media(LibVlc, mediaFI.FullName, FromType.FromPath);
await media.Parse();
Play(media);
}
void Play(Media media) {
var artworkPath = media.Meta(MetadataType.ArtworkURL);
Bitmap? artwork = null;
if (artworkPath is not null) {
var artworkUri = new Uri(artworkPath);
var artworkFI = new FileInfo(artworkUri.LocalPath);
artwork = artworkFI.Exists ? new Bitmap(artworkFI.FullName) : null;
}
MediaPlayer.Media = media;
videoView.BackgroundImage = artwork;
MediaPlayer.Play();
}
`
However, if I try to play an MP3 stream, using:`
// stream, media, mediaInput & artwork must be disposed after playing media
async Task PlayMediaStream(Stream stream) {
var mediaInput = new StreamMediaInput(stream);
var media = new Media(LibVlc, mediaInput);
await media.Parse(MediaParseOptions.ParseLocal); // and combinations with MediaParseOptions.ParseNetwork, MediaParseOptions.FetchNetwork - none worked
Play(media);
}
` The Artwork is always null. I know the stream has artwork; I can save it to file and fetch it using my 'PlayMediaFile()' method. Is there any way to fetch artwork from an MP3 media stream (short of writing it to a temp file and loading that)?
After taking a look at the source code VideoLan VLC repository, more specifically the preparser.c file, it appears that this behavior is intentional.
When the preparser is preparing the tasks for the callbacks (see the vlc_preparser_Push() function, the options (map to the MediaParseOptions
) are evaluated.
If the current Media Item is not a Local File, Directory or PlayList, then the parsing is skipped (sets an ITEM_PREPARSE_SKIPPED
flag) and returns success immediately.
This is presumably related to an implementation choice that considers a fast reaction to streaming request more important than buffering enough to get to the section where the metadata is stored.
Of course, this happens because the MediaInput
abstract class and the derived StreamMediaInput
behave like this.
Nothing stops you from deriving a custom stream handler from MediaInput
and provide an alternative buffering behavior, keeping track of the current Read position and buffering further to complete the stream and get the metadata way before the playback is completed.
Then parse the metadata and notify, e.g., via events, that the parsing is complete.
Easy wording it, not so simple in practice. Requires a lot of testing.
I've teste all combinations of MediaParseOptions
anyway, to see what happens (because this is the LibVLCSharp implementation), but the ParsedStatus
is always MediaParsedStatus.Skipped
, as the source code suggests it would be.
I've changed the code a bit while testing, to be more IDisposable compliant. Further testing in this department is needed, because of the fire-and-forget nature of the MediaPlayer's functionality.
More specifically, the StreamMediaInput
and Media
object must be disposed before a new stream is played. The current implementation doesn't do that (no finalizers either)
Image? Artwork { get; set; }
LibVLC libVlc = new LibVLC();
MediaPlayer? mediaPlayer = null;
async Task PlayMediaStream(Stream stream) {
mediaPlayer?.Dispose();
mediaPlayer = new MediaPlayer(libVlc);
Artwork?.Dispose();
Artwork = null;
// Both StreamMediaInput and Media must be disposed when the playback ends
var mediaInput = new StreamMediaInput(stream);
var media = new Media(libVlc, mediaInput);
media.ParsedChanged += (s, a) => {
if (a.ParsedStatus == MediaParsedStatus.Done) {
var artworkPath = media.Meta(MetadataType.ArtworkURL);
if (artworkPath is not null) {
var artworkFI = new FileInfo(new Uri(artworkPath).LocalPath);
if (artworkFI.Exists) {
Artwork = new Bitmap(artworkFI.FullName);
}
}
}
videoView.BackgroundImage = Artwork;
};
// Whatever combination
await media.Parse(MediaParseOptions.FetchLocal | MediaParseOptions.ParseLocal |
MediaParseOptions.FetchNetwork | MediaParseOptions.ParseNetwork);
mediaPlayer.Media = media;
var success = mediaPlayer.Play();
}