From 2bda927298d3491dcfe917de81b43ff85dd0c32d Mon Sep 17 00:00:00 2001 From: Jim906 Date: Tue, 12 Apr 2022 15:55:29 -0400 Subject: [PATCH] MediaPlayer: add .pls playlist support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add PlaylistFileReader class and derived M3uReader and PlsReader classes. * Move most of the code from Playlist::AppendM3uToPlaylist to PlaylistFileReader::_AppendItemToPlaylist * For each File line in the .pls file, a PlaylistItem is added to the MediaPlayer playlist. * For each Title/Length line, the data is applied to the most recently added PlaylistItem. * The NumberOfEntries and Version lines are read to make them available for future use, but currently they have no effect. * Fixes #6813 Change-Id: Ifa23d0df2e4d5b466aa7b85649a78276cff986ef Reviewed-on: https://review.haiku-os.org/c/haiku/+/5201 Tested-by: Commit checker robot Reviewed-by: Jérôme Duval --- src/apps/mediaplayer/Jamfile | 3 +- src/apps/mediaplayer/playlist/Playlist.cpp | 59 +---- src/apps/mediaplayer/playlist/Playlist.h | 4 +- .../playlist/PlaylistFileReader.cpp | 230 ++++++++++++++++++ .../mediaplayer/playlist/PlaylistFileReader.h | 62 +++++ 5 files changed, 304 insertions(+), 54 deletions(-) create mode 100644 src/apps/mediaplayer/playlist/PlaylistFileReader.cpp create mode 100644 src/apps/mediaplayer/playlist/PlaylistFileReader.h diff --git a/src/apps/mediaplayer/Jamfile b/src/apps/mediaplayer/Jamfile index bba251d3d2..1d2e257519 100644 --- a/src/apps/mediaplayer/Jamfile +++ b/src/apps/mediaplayer/Jamfile @@ -59,7 +59,7 @@ for architectureObject in [ MultiArchSubDirSetup ] { VideoSupplier.cpp VideoTarget.cpp - # plylist + # playlist CopyPLItemsCommand.cpp FilePlaylistItem.cpp ImportPLItemsCommand.cpp @@ -69,6 +69,7 @@ for architectureObject in [ MultiArchSubDirSetup ] { PlaylistItem.cpp PlaylistListView.cpp PlaylistObserver.cpp + PlaylistFileReader.cpp PlaylistWindow.cpp PLItemsCommand.cpp RandomizePLItemsCommand.cpp diff --git a/src/apps/mediaplayer/playlist/Playlist.cpp b/src/apps/mediaplayer/playlist/Playlist.cpp index 7f4e63165b..29e844bb37 100644 --- a/src/apps/mediaplayer/playlist/Playlist.cpp +++ b/src/apps/mediaplayer/playlist/Playlist.cpp @@ -418,7 +418,7 @@ Playlist::RemoveListener(Listener* listener) void Playlist::AppendItems(const BMessage* refsReceivedMessage, int32 appendIndex, - bool sortItems) + bool sortItems) { // the playlist is replaced by the refs in the message // or the refs are appended at the appendIndex @@ -461,11 +461,15 @@ Playlist::AppendItems(const BMessage* refsReceivedMessage, int32 appendIndex, } else { if (_IsQuery(type)) AppendQueryToPlaylist(ref, &subPlaylist); - else if (_IsM3u(ref)) - AppendM3uToPlaylist(ref, &subPlaylist); else { - if (!_ExtraMediaExists(this, ref)) { - AppendToPlaylistRecursive(ref, &subPlaylist); + PlaylistFileReader* reader = PlaylistFileReader::GenerateReader(ref); + if (reader != NULL) { + reader->AppendToPlaylist(ref, &subPlaylist); + delete reader; + } else { + if (!_ExtraMediaExists(this, ref)) { + AppendToPlaylistRecursive(ref, &subPlaylist); + } } } @@ -577,42 +581,6 @@ Playlist::AppendPlaylistToPlaylist(const entry_ref& ref, Playlist* playlist) } -/*static*/ void -Playlist::AppendM3uToPlaylist(const entry_ref& ref, Playlist* playlist) -{ - BFile file(&ref, B_READ_ONLY); - FileReadWrite lineReader(&file); - - BString line; - while (lineReader.Next(line)) { - if (line.FindFirst("#") != 0) { - BPath path(line.String()); - entry_ref refPath; - status_t err; - - if ((err = get_ref_for_path(path.Path(), &refPath)) == B_OK) { - PlaylistItem* item - = new (std::nothrow) FilePlaylistItem(refPath); - if (item == NULL || !playlist->AddItem(item)) - delete item; - } else { - BUrl url(line.String()); - if (url.IsValid()) { - PlaylistItem* item - = new (std::nothrow) UrlPlaylistItem(url); - if (item == NULL || !playlist->AddItem(item)) - delete item; - } else { - printf("Error - %s: [%" B_PRIx32 "]\n", strerror(err), err); - } - } - } - - line.Truncate(0); - } -} - - /*static*/ void Playlist::AppendQueryToPlaylist(const entry_ref& ref, Playlist* playlist) { @@ -760,15 +728,6 @@ Playlist::_IsPlaylist(const BString& mimeString) } -/*static*/ bool -Playlist::_IsM3u(const entry_ref& ref) -{ - BString path(BPath(&ref).Path()); - return path.FindLast(".m3u") == path.CountChars() - 4 - || path.FindLast(".m3u8") == path.CountChars() - 5; -} - - /*static*/ bool Playlist::_IsQuery(const BString& mimeString) { diff --git a/src/apps/mediaplayer/playlist/Playlist.h b/src/apps/mediaplayer/playlist/Playlist.h index 35fb47b36d..07569972ab 100644 --- a/src/apps/mediaplayer/playlist/Playlist.h +++ b/src/apps/mediaplayer/playlist/Playlist.h @@ -17,6 +17,7 @@ #include "FilePlaylistItem.h" #include "PlaylistItem.h" #include "UrlPlaylistItem.h" +#include "PlaylistFileReader.h" class BDataIO; class BMessage; @@ -102,8 +103,6 @@ public: Playlist* playlist); static void AppendPlaylistToPlaylist(const entry_ref& ref, Playlist* playlist); - static void AppendM3uToPlaylist(const entry_ref& ref, - Playlist* playlist); static void AppendQueryToPlaylist(const entry_ref& ref, Playlist* playlist); @@ -126,7 +125,6 @@ private: static bool _IsTextPlaylist(const BString& mimeString); static bool _IsBinaryPlaylist(const BString& mimeString); static bool _IsPlaylist(const BString& mimeString); - static bool _IsM3u(const entry_ref& ref); static bool _IsQuery(const BString& mimeString); static BString _MIMEString(const entry_ref* ref); static void _BindExtraMedia(PlaylistItem* item); diff --git a/src/apps/mediaplayer/playlist/PlaylistFileReader.cpp b/src/apps/mediaplayer/playlist/PlaylistFileReader.cpp new file mode 100644 index 0000000000..2cd06676ad --- /dev/null +++ b/src/apps/mediaplayer/playlist/PlaylistFileReader.cpp @@ -0,0 +1,230 @@ +/* + * PlaylistFileReader.cpp - Media Player for the Haiku Operating System + * + * Copyright (C) 2006 Marcus Overhagen + * Copyright (C) 2007-2009 Stephan Aßmus (MIT ok) + * Copyright (C) 2008-2009 Fredrik Modéen <[FirstName]@[LastName].se> (MIT ok) + * + * Released under the terms of the MIT license. + */ + +#include "PlaylistFileReader.h" + +#include +#include +#include +#include + +#include +#include + +#include "FileReadWrite.h" +#include "Playlist.h" + +using std::isdigit; +using std::nothrow; + + +/*static*/ PlaylistFileReader* +PlaylistFileReader::GenerateReader(const entry_ref& ref) +{ + PlaylistFileType type = _IdentifyType(ref); + PlaylistFileReader* reader = NULL; + if (type == m3u) + reader = new (nothrow) M3uReader; + else if (type == pls) + reader = new (nothrow) PlsReader; + return reader; +} + + +int32 +PlaylistFileReader::_AppendItemToPlaylist(const BString& entry, Playlist* playlist) +{ + bool validItem = true; + int32 assignedIndex = -1; + BPath path(entry.String()); + entry_ref refPath; + status_t err = get_ref_for_path(path.Path(), &refPath); + PlaylistItem* item = NULL; + + // Create a PlaylistItem if entry is a valid file path or URL + if (err == B_OK) + item = new (nothrow) FilePlaylistItem(refPath); + else { + BUrl url(entry); + if (url.IsValid()) + item = new (nothrow) UrlPlaylistItem(url); + else { + printf("Error - %s: [%" B_PRIx32 "]\n", strerror(err), err); + validItem = false; + } + } + + // If creation of a PlaylistItem was attempted, try to add it to the Playlist + if (validItem) { + if (item == NULL) + delete item; + else { + bool itemAdded = playlist->AddItem(item); + if (!itemAdded) + delete item; + else + assignedIndex = playlist->IndexOf(item); + } + } + + return assignedIndex; +} + + +/*static*/ PlaylistFileType +PlaylistFileReader::_IdentifyType(const entry_ref& ref) +{ + BString path(BPath(&ref).Path()); + PlaylistFileType type = unknown; + if (path.FindLast(".m3u") == path.CountChars() - 4 + || path.FindLast(".m3u8") == path.CountChars() - 5) + type = m3u; + else if (path.FindLast(".pls") == path.CountChars() - 4) + type = pls; + return type; +} + + +/*virtual*/ void +M3uReader::AppendToPlaylist(const entry_ref& ref, Playlist* playlist) +{ + BFile file(&ref, B_READ_ONLY); + FileReadWrite lineReader(&file); + + BString line; + while (lineReader.Next(line)) { + if (line.FindFirst("#") != 0) + // Current version of this function ignores all comment lines + _AppendItemToPlaylist(line, playlist); + line.Truncate(0); + } +} + + +/*virtual*/ void +PlsReader::AppendToPlaylist(const entry_ref& ref, Playlist* playlist) +{ + BString plsEntries; + // The total number of tracks in the PLS file, taken from the NumberOfEntries line. + // This variable is not used for anything at this time. + BString plsVersion; + // The version of the PLS standard used in this playlist file, taken from the Version line. + // This variable is not used for anything at this time. + int32 lastAssignedIndex = -1; + // The index that MediaPlayer assigned to the most recently added PlaylistItem. + // If an attempted assignment fails, this will be set to -1 again. + + BFile file(&ref, B_READ_ONLY); + FileReadWrite lineReader(&file); + BString line; + + // Check for the "[playlist]" header on the first line + lineReader.Next(line); + if (line != "[playlist]") { + printf("Error - Invalid .pls file\n"); + return; + } + line.Truncate(0); + + while (true) { + bool lineRead = lineReader.Next(line); + if (lineRead == false) + break; + + // All valid PLS lines after the header contain an equal sign + int32 equalIndex = line.FindFirst("="); + if (equalIndex == B_ERROR) { + line.Truncate(0); + continue; + } + + BString lineType; + // Will be set for each line to one of: File, Title, Length, NumberOfEntries, Version + BString trackNumber; + // Number of the track being processed, using the (one-based) explicit numbering + // from the .pls file + if (isdigit(line[equalIndex - 1])) { + // Distinguish between lines that specify a track number, and those that don't + int32 trackIndex = equalIndex - 1; + // The string index where the track number begins + while (isdigit(line[trackIndex - 1])) + trackIndex--; + line.CopyInto(lineType, 0, trackIndex); + line.CopyInto(trackNumber, trackIndex, equalIndex - trackIndex); + } else { + line.CopyInto(lineType, 0, equalIndex); + } + + BString lineContent; + // Everything after the equal sign + line.CopyInto(lineContent, equalIndex + 1, line.Length() - (equalIndex + 1)); + + if (lineType == "File") { + lastAssignedIndex = _AppendItemToPlaylist(lineContent, playlist); + // A File line may be followed by optional Title and/or Length lines. + } else if (lineType == "Title") { + _ParseTitleLine(lineContent, playlist, lastAssignedIndex); + } else if (lineType == "Length") { + _ParseLengthLine(lineContent, playlist, lastAssignedIndex); + // The file should include one NumberOfEntries line and one Version line. + } else if (lineType == "NumberOfEntries") { + plsEntries = lineContent; + } else if (lineType == "Version") { + plsVersion = lineContent; + } else { + // Ignore the line + } + + line.Truncate(0); + } +} + + +status_t +PlsReader::_ParseTitleLine(const BString& title, Playlist* playlist, + const int32 lastAssignedIndex) +{ + status_t err; + if (lastAssignedIndex != -1) { + // Only use this Title if most recent File was successfully added to the playlist. + err = playlist->ItemAt(lastAssignedIndex)->SetAttribute( + PlaylistItem::ATTR_STRING_TITLE, title); + } else + err = B_CANCELED; + + if (err != B_OK) + printf("Error - %s: [%" B_PRIx32 "]\n", strerror(err), err); + + return err; +} + + +status_t +PlsReader::_ParseLengthLine(const BString& length, Playlist* playlist, + const int32 lastAssignedIndex) +{ + status_t err; + if (lastAssignedIndex != -1) + { + int64 lengthInt = strtoll(length.String(), NULL, 10); + // Track length in seconds, or -1 for an infinite streaming track. + + err = playlist->ItemAt(lastAssignedIndex)->SetAttribute( + PlaylistItem::ATTR_INT64_DURATION, lengthInt); + // This does nothing if the track in question is streaming, because + // UrlPlaylistItem::SetAttribute(const Attribute&, const int32&) is not implemented. + } else + err = B_CANCELED; + + if (err != B_OK) + printf("Error - %s: [%" B_PRIx32 "]\n", strerror(err), err); + + return err; +} diff --git a/src/apps/mediaplayer/playlist/PlaylistFileReader.h b/src/apps/mediaplayer/playlist/PlaylistFileReader.h new file mode 100644 index 0000000000..008ee9e6f9 --- /dev/null +++ b/src/apps/mediaplayer/playlist/PlaylistFileReader.h @@ -0,0 +1,62 @@ +/* + * PlaylistFileReader.h - Media Player for the Haiku Operating System + * + * Copyright (C) 2006 Marcus Overhagen + * Copyright (C) 2007-2009 Stephan Aßmus (MIT ok) + * Copyright (C) 2008-2009 Fredrik Modéen <[FirstName]@[LastName].se> (MIT ok) + * + * Released under the terms of the MIT license. + */ +#ifndef __PLAYLIST_FILE_READER_H +#define __PLAYLIST_FILE_READER_H + + +#include + +class BString; +struct entry_ref; +class Playlist; + +enum PlaylistFileType {m3u, pls, unknown}; + + +class PlaylistFileReader { +public: + virtual ~PlaylistFileReader() {} + // Defined to enable deletion of PlaylistFileReader* target objects. + + virtual void AppendToPlaylist(const entry_ref& ref, + Playlist* playlist) = 0; + static PlaylistFileReader* GenerateReader(const entry_ref& ref); + // Returns a pointer to an object of the appropriate derived class, + // or returns NULL if the argument is not a valid playlist file. + +protected: + int32 _AppendItemToPlaylist(const BString& entry, Playlist* playlist); + // Returns the track's playlist index if it was successfully added, else returns -1. + // BString& entry is a (absolute or relative) file path or URL + +private: + static PlaylistFileType _IdentifyType(const entry_ref& ref); +}; + + +class M3uReader : public PlaylistFileReader { +public: + virtual void AppendToPlaylist(const entry_ref& ref, Playlist* playlist); +}; + + +class PlsReader : public PlaylistFileReader { +public: + virtual void AppendToPlaylist(const entry_ref& ref, + Playlist* playlist); +private: + status_t _ParseTitleLine(const BString& title, Playlist* playlist, + const int32 lastAssignedIndex); + status_t _ParseLengthLine(const BString& length, Playlist* playlist, + const int32 lastAssignedIndex); +}; + + +#endif