MediaPlayer: add .pls playlist support

* 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 <no-reply+buildbot@haiku-os.org>
Reviewed-by: Jérôme Duval <jerome.duval@gmail.com>
This commit is contained in:
Jim906 2022-04-12 15:55:29 -04:00 committed by Jérôme Duval
parent 06d109315e
commit 2bda927298
5 changed files with 304 additions and 54 deletions

View File

@ -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

View File

@ -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)
{

View File

@ -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);

View File

@ -0,0 +1,230 @@
/*
* PlaylistFileReader.cpp - Media Player for the Haiku Operating System
*
* Copyright (C) 2006 Marcus Overhagen <marcus@overhagen.de>
* Copyright (C) 2007-2009 Stephan Aßmus <superstippi@gmx.de> (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 <cctype>
#include <new>
#include <stdio.h>
#include <strings.h>
#include <Path.h>
#include <Url.h>
#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;
}

View File

@ -0,0 +1,62 @@
/*
* PlaylistFileReader.h - Media Player for the Haiku Operating System
*
* Copyright (C) 2006 Marcus Overhagen <marcus@overhagen.de>
* Copyright (C) 2007-2009 Stephan Aßmus <superstippi@gmx.de> (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 <SupportDefs.h>
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