From c86d7e0afd2c8fd9d4a9a8d4d2cbe0d4b92fdc3d Mon Sep 17 00:00:00 2001 From: Randy Heit <rheit@zdoom.fake> Date: Wed, 30 Apr 2008 05:36:24 +0000 Subject: [PATCH] - Fixed: FString::StripRight() stripped the final character of the string if there were no designated characters to strip at the end of it. - Added support for Shoutcast/Icecast playlists. - Added an error message when a playlist could not be opened. - Added support for PLS format playlists, in addition to M3U. - Changed FPlayList to use an array of FStrings. - Fixed: Playlists required every song to be specified by an absolute path. SVN r951 (trunk) --- docs/rh-log.txt | 7 ++ src/s_playlist.cpp | 144 ++++++++++++++++++++----------------- src/s_playlist.h | 8 +-- src/s_sound.cpp | 82 ++++++++++++++------- src/sound/fmodsound.cpp | 123 ++++++++++++++++++++++++++++--- src/sound/i_music.cpp | 17 +++++ src/sound/i_music.h | 1 + src/sound/i_musicinterns.h | 3 +- src/sound/i_sound.h | 13 ++-- src/sound/music_stream.cpp | 12 +--- src/zstring.cpp | 6 +- src/zstring.h | 2 +- 12 files changed, 292 insertions(+), 126 deletions(-) diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 03cd7257b..9aa5ce8ba 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,4 +1,11 @@ April 29, 2008 +- Fixed: FString::StripRight() stripped the final character of the string if + there were no designated characters to strip at the end of it. +- Added support for Shoutcast/Icecast playlists. +- Added an error message when a playlist could not be opened. +- Added support for PLS format playlists, in addition to M3U. +- Changed FPlayList to use an array of FStrings. +- Fixed: Playlists required every song to be specified by an absolute path. - Fixed a copy-and-paste error in win32/i_main.cpp for 64-bit mode. - Tweaked OPL centering a little. diff --git a/src/s_playlist.cpp b/src/s_playlist.cpp index 16a5f1753..5f68188e8 100644 --- a/src/s_playlist.cpp +++ b/src/s_playlist.cpp @@ -33,127 +33,143 @@ */ #include <stdlib.h> +#include <errno.h> #include "cmdlib.h" #include "s_playlist.h" #include "templates.h" +#include "v_text.h" FPlayList::FPlayList (const char *path) { - Songs = NULL; - SongList = NULL; ChangeList (path); } FPlayList::~FPlayList () { - if (Songs) delete[] Songs; - if (SongList) delete[] SongList; } bool FPlayList::ChangeList (const char *path) { - char linebuff[256]; - size_t songlengths; - int songcount; + FString playlistdir; + FString song; FILE *file; + bool first; + bool pls; + int i; - if (Songs) - { - delete[] Songs; - Songs = NULL; - } - if (SongList) - { - delete[] SongList; - SongList = NULL; - } + Songs.Clear(); Position = 0; - NumSongs = 0; - if ( (file = fopen (path, "r")) == NULL) - return false; - - songlengths = 0; - songcount = 0; - - while (NextLine (file, linebuff, sizeof(linebuff))) + if ( (file = fopen (path, "rb")) == NULL) { - songcount++; - songlengths += strlen (linebuff) + 1; + Printf ("Could not open "TEXTCOLOR_BOLD"%s"TEXTCOLOR_NORMAL": %s\n", path, strerror(errno)); + return false; } - rewind (file); - - if (songcount > 0) + first = true; + pls = false; + playlistdir = ExtractFilePath(path); + while ((song = NextLine(file)).IsNotEmpty()) { - Songs = new char *[songcount]; - SongList = new char[songlengths]; - NumSongs = songcount; - songlengths = 0; - - for (songcount = 0; songcount < NumSongs && - NextLine (file, linebuff, sizeof(linebuff)); songcount++) + if (first) { - size_t len = strlen (linebuff) + 1; - - memcpy (SongList + songlengths, linebuff, len); - Songs[songcount] = SongList + songlengths; - songlengths += len; + first = false; + // Check for ID tags. + if (song.Compare("[playlist]") == 0) + { + pls = true; + continue; + } } - NumSongs = songcount; - } + // For a .PLS file, skip anything that doesn't start with File[0-9]+= + if (pls) + { + if (strncmp(song, "File", 4) != 0) + { + continue; + } + for (i = 4; song[i] >= '0' && song[i] <= '9'; ++i) + { + } + if (song[i] != '=') + { + continue; + } + song = song.Mid(i + 1); + } + // Check for relative paths. + long slashpos = song.IndexOf('/'); + + if (slashpos == 0) + { + // First character is a slash, so it's absolute. + } +#ifdef _WIN32 + else if (slashpos == 2 && song[1] == ':') + { + // Name is something like X:/, so it's absolute. + } +#endif + else if (song.IndexOf("://") == slashpos - 1) + { + // Name is a URL, so it's absolute. + } + else + { + // Path is relative; append it to the playlist directory. + song = playlistdir + song; + } + Songs.Push(song); + } fclose (file); - return NumSongs > 0; + return Songs.Size() != 0; } -bool FPlayList::NextLine (FILE *file, char *buffer, int n) +FString FPlayList::NextLine (FILE *file) { + char buffer[512]; char *skipper; do { - if (NULL == fgets (buffer, n, file)) - return false; + if (NULL == fgets (buffer, countof(buffer), file)) + return ""; for (skipper = buffer; *skipper != 0 && *skipper <= ' '; skipper++) ; } while (*skipper == '#' || *skipper == 0); - if (skipper > buffer) - memmove (buffer, skipper, strlen (skipper)+1); - - if (buffer[strlen (buffer)-1] == '\n') - buffer[strlen (buffer)-1] = 0; - - FixPathSeperator (buffer); - - return true; + FString str(skipper); + str.StripRight("\r\n"); + FixPathSeperator(str); + return str; } // Shuffles the playlist and resets the position to the start void FPlayList::Shuffle () { + unsigned int numsongs = Songs.Size(); int i; - for (i = 0; i < NumSongs; ++i) + for (i = 0; i < numsongs; ++i) { - swap (Songs[i], Songs[(rand() % (NumSongs - i)) + i]); + swap (Songs[i], Songs[(rand() % (numsongs - i)) + i]); } Position = 0; } int FPlayList::GetNumSongs () const { - return NumSongs; + return (int)Songs.Size(); } int FPlayList::SetPosition (int position) { - if ((unsigned)position >= (unsigned)NumSongs) + if ((unsigned)position >= Songs.Size()) { Position = 0; } @@ -172,7 +188,7 @@ int FPlayList::GetPosition () const int FPlayList::Advance () { - if (++Position >= NumSongs) + if (++Position >= Songs.Size()) { Position = 0; } @@ -184,7 +200,7 @@ int FPlayList::Backup () { if (--Position < 0) { - Position = NumSongs - 1; + Position = Songs.Size() - 1; } DPrintf ("Playlist backed up to song %d\n", Position); return Position; @@ -192,7 +208,7 @@ int FPlayList::Backup () const char *FPlayList::GetSong (int position) const { - if ((unsigned)position >= (unsigned)NumSongs) + if ((unsigned)position >= Songs.Size()) return NULL; return Songs[position]; diff --git a/src/s_playlist.h b/src/s_playlist.h index 6f1568d4d..3223f19e1 100644 --- a/src/s_playlist.h +++ b/src/s_playlist.h @@ -51,12 +51,10 @@ public: const char *GetSong (int position) const; private: - static bool NextLine (FILE *file, char *buffer, int n); + static FString NextLine (FILE *file); - int Position; - int NumSongs; - char **Songs; // Pointers into SongList - char *SongList; + unsigned int Position; + TArray<FString> Songs; }; #endif //__S_PLAYLIST_H__ diff --git a/src/s_sound.cpp b/src/s_sound.cpp index 7214753ff..dd3d5e2cf 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -1181,10 +1181,12 @@ void S_UpdateSounds (void *listener_p) if (GSnd == NULL) return; - // [RH] Update playlist - if (PlayList && - mus_playing.handle && - !I_QrySongPlaying(mus_playing.handle)) + // [RH] Update music and/or playlist. I_QrySongPlaying() must be called + // to attempt to reconnect to broken net streams and to advance the + // playlist when the current song finishes. + if (mus_playing.handle != NULL && + !I_QrySongPlaying(mus_playing.handle) && + PlayList) { PlayList->Advance(); S_ActivatePlayList(false); @@ -1335,42 +1337,64 @@ bool S_ChangeMusic (const char *musicname, int order, bool looping, bool force) int lumpnum = -1; int offset, length; int device = MDEV_DEFAULT; - + void *handle = NULL; int *devp = MidiDevices.CheckKey(FName(musicname)); if (devp != NULL) device = *devp; + // Strip off any leading file:// component. + if (strncmp(musicname, "file://", 7) == 0) + { + musicname += 7; + } + if (!FileExists (musicname)) { if ((lumpnum = Wads.CheckNumForFullName (musicname, true, ns_music)) == -1) { - Printf ("Music \"%s\" not found\n", musicname); - return false; - } - if (!Wads.IsUncompressedFile(lumpnum)) - { - // We must cache the music data and use it from memory. - - // shut down old music before reallocating and overwriting the cache! - S_StopMusic (true); - - offset = -1; // this tells the low level code that the music - // is being used from memory - length = Wads.LumpLength (lumpnum); - if (length == 0) + if (strstr(musicname, "://") > musicname) { + // Looks like a URL; try it as such. + handle = I_RegisterURLSong(musicname); + if (handle == NULL) + { + Printf ("Could not open \"%s\"\n", musicname); + return false; + } + } + else + { + Printf ("Music \"%s\" not found\n", musicname); return false; } - musiccache.Resize(length); - Wads.ReadLump(lumpnum, &musiccache[0]); } - else + if (handle == NULL) { - offset = Wads.GetLumpOffset (lumpnum); - length = Wads.LumpLength (lumpnum); - if (length == 0) + if (!Wads.IsUncompressedFile(lumpnum)) { - return false; + // We must cache the music data and use it from memory. + + // shut down old music before reallocating and overwriting the cache! + S_StopMusic (true); + + offset = -1; // this tells the low level code that the music + // is being used from memory + length = Wads.LumpLength (lumpnum); + if (length == 0) + { + return false; + } + musiccache.Resize(length); + Wads.ReadLump(lumpnum, &musiccache[0]); + } + else + { + offset = Wads.GetLumpOffset (lumpnum); + length = Wads.LumpLength (lumpnum); + if (length == 0) + { + return false; + } } } } @@ -1394,7 +1418,11 @@ bool S_ChangeMusic (const char *musicname, int order, bool looping, bool force) } // load & register it - if (offset != -1) + if (handle != NULL) + { + mus_playing.handle = handle; + } + else if (offset != -1) { mus_playing.handle = I_RegisterSong (lumpnum != -1 ? Wads.GetWadFullName (Wads.GetLumpFile (lumpnum)) : diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index c6f90b234..f7e87d2dd 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -208,7 +208,8 @@ static const char *OpenStateNames[] = "Error", "Connecting", "Buffering", - "Seeking" + "Seeking", + "Streaming" }; // CODE -------------------------------------------------------------------- @@ -266,9 +267,9 @@ static const char *Enum_NameForNum(const FEnumList *list, int num) class FMODStreamCapsule : public SoundStream { public: - FMODStreamCapsule(FMOD::Sound *stream, FMODSoundRenderer *owner) + FMODStreamCapsule(FMOD::Sound *stream, FMODSoundRenderer *owner, const char *url) : Owner(owner), Stream(NULL), Channel(NULL), - UserData(NULL), Callback(NULL), Ended(false) + UserData(NULL), Callback(NULL), Ended(false), URL(url) { SetStream(stream); } @@ -280,6 +281,10 @@ public: ~FMODStreamCapsule() { + if (Channel != NULL) + { + Channel->stop(); + } if (Stream != NULL) { Stream->release(); @@ -303,7 +308,11 @@ public: { FMOD_RESULT result; - Stream->setMode(looping ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF); + if (URL.IsNotEmpty()) + { // Net streams cannot be looped, because they cannot be seeked. + looping = false; + } + Stream->setMode((looping ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF) | FMOD_SOFTWARE | FMOD_2D); result = Owner->Sys->playSound(FMOD_CHANNEL_FREE, Stream, true, &Channel); if (result != FMOD_OK) { @@ -320,6 +329,11 @@ public: Channel->setReverbProperties(&reverb); } Channel->setPaused(false); + Ended = false; + JustStarted = true; + Starved = false; + Loop = looping; + Volume = volume; return true; } @@ -352,12 +366,82 @@ public: return 0; } + bool IsEnded() + { + bool is; + FMOD_OPENSTATE openstate = FMOD_OPENSTATE_MAX; + bool starving; + + if (Stream == NULL) + { + return true; + } + if (FMOD_OK != Stream->getOpenState(&openstate, NULL, &starving)) + { + openstate = FMOD_OPENSTATE_ERROR; + } + if (openstate == FMOD_OPENSTATE_ERROR) + { + if (Channel != NULL) + { + Channel->stop(); + Channel = NULL; + } + return true; + } + if (Channel != NULL && (FMOD_OK != Channel->isPlaying(&is) || is == false)) + { + return true; + } + if (Ended) + { + Channel->stop(); + Channel = NULL; + return true; + } + if (URL.IsNotEmpty() && !JustStarted && openstate == FMOD_OPENSTATE_READY) + { + // Reconnect the stream, since it seems to have stalled. + // The only way to do this appears to be to completely recreate it. + FMOD_RESULT result; + + Channel->stop(); + Stream->release(); + Channel = NULL; + Stream = NULL; + Owner->Sys->setStreamBufferSize(64*1024, FMOD_TIMEUNIT_RAWBYTES); + // Open the stream asynchronously, so we don't hang the game while trying to reconnect. + // (It would be nice to do the initial open asynchronously as well, but I'd need to rethink + // the music system design to pull that off.) + result = Owner->Sys->createSound(URL, (Loop ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF) | FMOD_SOFTWARE | FMOD_2D | + FMOD_CREATESTREAM | FMOD_NONBLOCKING, NULL, &Stream); + JustStarted = true; + Owner->Sys->setStreamBufferSize(16*1024, FMOD_TIMEUNIT_RAWBYTES); + return result != FMOD_OK; + } + if (JustStarted && openstate == FMOD_OPENSTATE_STREAMING) + { + JustStarted = false; + } + if (JustStarted && Channel == NULL && openstate == FMOD_OPENSTATE_READY) + { + return !Play(Loop, Volume); + } + if (starving != Starved) + { // Mute the sound if it's starving. + Channel->setVolume(starving ? 0 : Volume); + Starved = starving; + } + return false; + } + void SetVolume(float volume) { - if (Channel != NULL) + if (Channel != NULL && !Starved) { Channel->setVolume(volume); } + Volume = volume; } // Sets the current order number for a MOD-type song, or the position in ms @@ -387,12 +471,17 @@ public: if (FMOD_OK == Stream->getOpenState(&openstate, &percentbuffered, &starving)) { - stats = (openstate <= FMOD_OPENSTATE_SEEKING ? OpenStateNames[openstate] : "Unknown state"); + stats = (openstate <= FMOD_OPENSTATE_STREAMING ? OpenStateNames[openstate] : "Unknown state"); stats.AppendFormat(",%3d%% buffered, %s", percentbuffered, starving ? "Starving" : "Well-fed"); } if (Channel != NULL && FMOD_OK == Channel->getPosition(&position, FMOD_TIMEUNIT_MS)) { - stats.AppendFormat(", %d ms", position); + stats.AppendFormat(", %d", position); + if (FMOD_OK == Stream->getLength(&position, FMOD_TIMEUNIT_MS)) + { + stats.AppendFormat("/%d", position); + } + stats += " ms"; } return stats; } @@ -428,7 +517,12 @@ private: FMOD::Channel *Channel; void *UserData; SoundStreamCallback Callback; + FString URL; bool Ended; + bool JustStarted; + bool Starved; + bool Loop; + float Volume; }; //========================================================================== @@ -1086,6 +1180,7 @@ SoundStream *FMODSoundRenderer::OpenStream(const char *filename_or_data, int fla FMOD_CREATESOUNDEXINFO exinfo = { sizeof(exinfo), }; FMOD::Sound *stream; FMOD_RESULT result; + bool url; mode = FMOD_SOFTWARE | FMOD_2D | FMOD_CREATESTREAM; if (flags & SoundStream::Loop) @@ -1104,7 +1199,19 @@ SoundStream *FMODSoundRenderer::OpenStream(const char *filename_or_data, int fla exinfo.dlsname = snd_midipatchset; } + url = (offset == 0 && length == 0 && strstr(filename_or_data, "://") > filename_or_data); + if (url) + { + // Use a larger buffer for URLs so that it's less likely to be effected + // by hiccups in the data rate from the remote server. + Sys->setStreamBufferSize(64*1024, FMOD_TIMEUNIT_RAWBYTES); + } result = Sys->createSound(filename_or_data, mode, &exinfo, &stream); + if (url) + { + // Restore standard buffer size. + Sys->setStreamBufferSize(16*1024, FMOD_TIMEUNIT_RAWBYTES); + } if (result == FMOD_ERR_FORMAT && exinfo.dlsname != NULL) { // FMOD_ERR_FORMAT could refer to either the main sound file or @@ -1119,7 +1226,7 @@ SoundStream *FMODSoundRenderer::OpenStream(const char *filename_or_data, int fla } if (result == FMOD_OK) { - return new FMODStreamCapsule(stream, this); + return new FMODStreamCapsule(stream, this, url ? filename_or_data : NULL); } return NULL; } diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 87cd99c6f..f09db0df1 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -259,6 +259,23 @@ void I_UnRegisterSong (void *handle) } } +void *I_RegisterURLSong (const char *url) +{ + StreamSong *song; + + if (GSnd == NULL) + { + return NULL; + } + song = new StreamSong(url, 0, 0); + if (song->IsValid()) + { + return song; + } + delete song; + return NULL; +} + void *I_RegisterSong (const char *filename, char *musiccache, int offset, int len, int device) { FILE *file; diff --git a/src/sound/i_music.h b/src/sound/i_music.h index 039e1cf2d..d517bd566 100644 --- a/src/sound/i_music.h +++ b/src/sound/i_music.h @@ -56,6 +56,7 @@ void I_ResumeSong (void *handle); // Registers a song handle to song data. void *I_RegisterSong (const char *file, char * musiccache, int offset, int length, int device); void *I_RegisterCDSong (int track, int cdid = 0); +void *I_RegisterURLSong (const char *url); // Called by anything that wishes to start music. // Plays a song, and when the song is done, diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 6edde4691..e07e488b0 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -452,10 +452,9 @@ public: FString GetStats(); protected: - StreamSong () : m_Stream(NULL), m_LastPos(0) {} + StreamSong () : m_Stream(NULL) {} SoundStream *m_Stream; - int m_LastPos; }; // MIDI file played with Timidity and possibly streamed through FMOD -------- diff --git a/src/sound/i_sound.h b/src/sound/i_sound.h index 35a6f5547..74965ac09 100644 --- a/src/sound/i_sound.h +++ b/src/sound/i_sound.h @@ -53,12 +53,13 @@ public: Loop = 16 }; - virtual bool Play (bool looping, float volume) = 0; - virtual void Stop () = 0; - virtual void SetVolume (float volume) = 0; - virtual bool SetPaused (bool paused) = 0; - virtual unsigned int GetPosition () = 0; - virtual bool SetPosition (int pos); + virtual bool Play(bool looping, float volume) = 0; + virtual void Stop() = 0; + virtual void SetVolume(float volume) = 0; + virtual bool SetPaused(bool paused) = 0; + virtual unsigned int GetPosition() = 0; + virtual bool IsEnded() = 0; + virtual bool SetPosition(int pos); virtual FString GetStats(); }; diff --git a/src/sound/music_stream.cpp b/src/sound/music_stream.cpp index 0c4e039ed..47a6cb08f 100644 --- a/src/sound/music_stream.cpp +++ b/src/sound/music_stream.cpp @@ -8,7 +8,6 @@ void StreamSong::Play (bool looping) if (m_Stream->Play (m_Looping, 1)) { m_Status = STATE_Playing; - m_LastPos = 0; } } @@ -65,18 +64,11 @@ bool StreamSong::IsPlaying () { if (m_Status != STATE_Stopped) { - if (m_Looping) - return true; - - int pos = m_Stream->GetPosition (); - - if (pos < m_LastPos) + if (m_Stream->IsEnded()) { - Stop (); + Stop(); return false; } - - m_LastPos = pos; return true; } return false; diff --git a/src/zstring.cpp b/src/zstring.cpp index 6c906a331..011ef517d 100644 --- a/src/zstring.cpp +++ b/src/zstring.cpp @@ -385,7 +385,7 @@ FString FString::Mid (size_t pos, size_t numChars) const { return FString(); } - if (pos + numChars > len) + if (pos + numChars > len || pos + numChars < pos) { numChars = len - pos; } @@ -631,7 +631,7 @@ void FString::StripLeft (const char *charset) void FString::StripRight () { size_t max = Len(), i; - for (i = max - 1; i-- > 0; ) + for (i = max; i-- > 0; ) { if (!isspace(Chars[i])) break; @@ -658,7 +658,7 @@ void FString::StripRight (const FString &charset) void FString::StripRight (const char *charset) { size_t max = Len(), i; - for (i = max - 1; i-- > 0; ) + for (i = max; i-- > 0; ) { if (!strchr (charset, Chars[i])) break; diff --git a/src/zstring.h b/src/zstring.h index ec21cfa53..749c51728 100644 --- a/src/zstring.h +++ b/src/zstring.h @@ -164,7 +164,7 @@ public: FString Left (size_t numChars) const; FString Right (size_t numChars) const; - FString Mid (size_t pos, size_t numChars) const; + FString Mid (size_t pos, size_t numChars = ~(size_t)0) const; long IndexOf (const FString &substr, long startIndex=0) const; long IndexOf (const char *substr, long startIndex=0) const;