static void _TrackFinished( TrackFinishedInfo info ) { Console.WriteLine( "TrackFinished: " + info.reason.ToString() ); Exception e = info.exception; if (null != e) Console.WriteLine( e.ToString() ); }
/// /// This is the entry point for the buffer processing thread. /// /// \todo This needs exception handling. /// void _Mp3ReaderThread() { Trace.WriteLine( "Hello", "MP3" ); Thread audioThread = null; try { audioThread = new Thread( new ThreadStart( _AudioThread ) ); audioThread.Priority = ThreadPriority.AboveNormal; audioThread.Start(); Trace.WriteLine( "Entering main loop", "MP3" ); _configMutex.WaitOne(); while (true) { // Execution will stop here if the parent thread tries to // change the configuration in some way. _configMutex.ReleaseMutex(); _configMutex.WaitOne(); switch (_state) { case State.STOP: Trace.WriteLine( " State.STOP", "MP3" ); // In case we were playing or whatever, be sure files and // streams are closed: if (_mp3Stream != null) { _mp3Stream.Close(); _mp3Stream = null; } // Stop the audio writer while we delete the estream. if (false == _audioThreadMutex.WaitOne( AUDIO_TIMEOUT, false )) { // If this happened, we probably want to kill the // audio thread and restart esd. :( throw new ApplicationException( "Timed out waiting for the audio mutex" ); } try { _ShutDownEstream(); } finally { _audioThreadMutex.ReleaseMutex(); } // Wait until the parent wakes us up. _configMutex.ReleaseMutex(); _fileToPlayEvent.WaitOne(); Trace.WriteLine( "STOP -> " + _state.ToString(), "MP3" ); _configMutex.WaitOne(); // We've got the _configMutex, so no race condition exists. // I think. Maybe. _fileToPlayEvent.Reset(); break; case State.PLAY_FILE_REQUEST: Trace.WriteLine( " State.PLAY_FILE_REQUEST", "MP3" ); if (_bufferSizeChanged || null == _estream) { // Stop the audio writer stream to handle buffer resize. if (false == _audioThreadMutex.WaitOne( AUDIO_TIMEOUT, false )) { // If this happened, we probably want to kill the // audio thread and restart esd. :( throw new ApplicationException( "Timed out waiting for the audio mutex" ); } try { if (!_StartUpEstream()) { _state = State.STOP; // stop! break; // * BREAK OUT ** } _CreateMp3Buffers(); _underflowEvent.Set(); _bufferSizeChanged = false; // no longer } finally { _audioThreadMutex.ReleaseMutex(); } } // Start playing if we can. if (_InternalStartNextFile() == false) _state = State.STOP; // Nothing in the queue else _state = State.PLAYING; break; case State.PLAYING: // Trace.WriteLine( " State.PLAYING", "MP3" ); Debug.Assert( null != _estream, "not created in PLAY_FILE_REQUEST?" ); // Wait for a free audio buffer Buffer buffer = _WaitForAndPopFreeBuffer(); // The Mp3Stream wrapper is still a bit flaky. Especially // it seems to throw exceptions at end-of-file sometimes, // probably trying to read garbage. Encase it in a // try/catch block: Exception playbackException = null; try { // Fill the buffer with goodness. buffer.validBytes = _mp3Stream.Read( buffer.mp3Buffer, 0, buffer.mp3Buffer.Length ); if (null != OnReadBuffer) OnReadBuffer( buffer.mp3Buffer, buffer.validBytes ); } catch (Exception e) { // I'm not sure, but I think we may have to destroy // and recreate the Mp3Stream to get it working again // here. Trace.WriteLine( "Problem Reading from MP3:" + e.ToString(), "MP3" ); // Flag the stream as finished. Heh! buffer.validBytes = 0; playbackException = e; // save for reporting } if (buffer.validBytes <= 0) { _PushFreeBuffer( buffer ); // Done with prev file: notify any listeners. Send them // the exception if something went wrong TrackFinishedInfo info; if (null == playbackException) { info = new TrackFinishedInfo( _playingTrack.index, TrackFinishedInfo.Reason.NORMAL ); } else { info = new TrackFinishedInfo ( _playingTrack.index, playbackException, TrackFinishedInfo.Reason.PLAY_ERROR ); } if (null != OnTrackFinished) OnTrackFinished( info ); if (_InternalStartNextFile() == false) _state = State.STOP; // end of file } else { _PushMp3Buffer( buffer ); // Loaded with sample goodness } break; case State.SHUTDOWN_REQUEST: Trace.WriteLine( " State.SHUTDOWN_REQUEST", "MP3" ); _configMutex.ReleaseMutex(); // Handle cleanup in the "finally" block return; default: break; } } } catch (Exception reasonForCrashing) { Trace.WriteLine( reasonForCrashing.ToString(), "MP3" ); } finally { if (null != audioThread) { // Try to get the audio thread mutex, but eventually give up // so we can clean up regardless. if (false == _audioThreadMutex.WaitOne( AUDIO_TIMEOUT, false )) { // Couldn't get mutex. This is bad. audioThread.Abort(); // Just kill the thread. } else { try { /// /// \todo Shut down audioThread properly (Join it) /// instead of calling Abort(). /// audioThread.Abort(); } finally { _audioThreadMutex.ReleaseMutex(); } } } if (null != _estream) { _estream.FreeWriteBuffer(); _estream.Close(); } // Don't want to exit the thread while holding this mutex, // do we? It's possible we aren't holding it, but certainly // we don't have to worry about releasing other threads' // claim. _configMutex.ReleaseMutex(); Trace.WriteLine( "bye", "MP3" ); } }
/// /// Called by the mp3 reader when a track is finished. /// /// \warning Called in the context of the reader thread! /// void _TrackFinishedCallback( TrackFinishedInfo info ) { _Trace( "[_TrackFinishedCallback]" ); ++ _changeCount; // Previous track could be nothing? if (null != info) { Trace.WriteLine( String.Format( "Track {0} finished. status:{1}", info.key, info.reason ) ); // Because of the threading, I don't know we can guarantee // this will never happen. Let's find out: Debug.Assert( info.key == currentTrack.key, "finished track != playing track" ); switch (info.reason) { case TrackFinishedInfo.Reason.NORMAL: case TrackFinishedInfo.Reason.USER_REQUEST: case TrackFinishedInfo.Reason.PLAY_ERROR: // What to do with this? // Log that this track played until such time as it either // failed or was cancelled. (Don't log on error, because it // didn't really play, did it?) _database.IncrementPlayCount( info.key ); break; case TrackFinishedInfo.Reason.OPEN_ERROR: // File probably missing // Ouch, potential deadlock. lock (_serializer) { // Update the ref to the current-playing track data PlayableData currentInfo = _PlaylistGetCurrent(); currentInfo.status = TrackStatus.MISSING; _database.SetTrackStatus ( info.key, StatusDatabase.TrackStatus.MISSING ); } break; default: throw new ApplicationException( "unexpected case in switch" ); } } if (_shouldBePlaying) // don't bother if we should be stopped { // Don't acquire the _serializer lock here, because this could // cause a deadlock! // Advance the playlist (_Playlist* functions are threadsafe) GotoNext( false ); } }