/// <summary> /// Callback function for the outputstream. /// </summary> /// <param name="streamHandle">Bass stream handle that requests sample data.</param> /// <param name="buffer">Buffer to write the sampledata to.</param> /// <param name="requestedBytes">Requested number of bytes.</param> /// <param name="userData"></param> /// <returns>Number of bytes read.</returns> private int OutputStreamWriteProc(int streamHandle, IntPtr buffer, int requestedBytes, IntPtr userData) { IInputSource inputSource; lock (_syncObj) { if (_state == SessionState.Reset) { return(0); } inputSource = _currentInputSource; if (inputSource == null) { _state = SessionState.Ended; return((int)BASSStreamProc.BASS_STREAMPROC_END); } } try { BassStream stream = inputSource.OutputStream; int read = stream.Read(buffer, requestedBytes); bool doCheckNextInputSource = false; lock (_syncObj) if (!_isAwaitingNextInputSource && stream.GetPosition() > stream.Length.Subtract(REQUEST_NEXT_ITEM_THRESHOLD)) { // Near end of the stream - make sure that next input source is available _isAwaitingNextInputSource = true; doCheckNextInputSource = true; } if (doCheckNextInputSource) { _playbackProcessor.CheckInputSourceAvailable(); } if (read > 0) { // Normal case, we have finished return(read); } // Old buffer ran out of samples - either we can get another valid input source below or we are finished. End wait state. _isAwaitingNextInputSource = false; // Nothing could be read from old input source. Second try: Next input source. IInputSource newInputSource = _playbackProcessor.PeekNextInputSource(); // Special treatment for CD drives: If the new input source is from the same audio CD drive, we must take the stream over BassCDTrackInputSource bcdtisOld = inputSource as BassCDTrackInputSource; BassCDTrackInputSource bcdtisNew = newInputSource as BassCDTrackInputSource; if (bcdtisOld != null && bcdtisNew != null) { if (bcdtisOld.SwitchTo(bcdtisNew)) { _playbackProcessor.ClearNextInputSource(); return(OutputStreamWriteProc(streamHandle, buffer, requestedBytes, userData)); } } lock (_syncObj) { _currentInputSource = null; _controller.ScheduleDisposeObject_Async(inputSource); if (newInputSource == null) { _state = SessionState.Ended; return((int)BASSStreamProc.BASS_STREAMPROC_END); } } if (!MatchesInputSource(newInputSource)) { // The next available input source is not compatible, so end our stream. The playback processor will start a new playback session later. lock (_syncObj) _state = SessionState.Ended; return((int)BASSStreamProc.BASS_STREAMPROC_END); } _playbackProcessor.ClearNextInputSource(); // Should be the contents of newInputSource lock (_syncObj) { _currentInputSource = newInputSource; _state = SessionState.Playing; } // Next try return(OutputStreamWriteProc(streamHandle, buffer, requestedBytes, userData)); } catch (Exception) { // We might come here due to a race condition. To avoid that, we would have to employ a new manual locking mechanism // to avoid that during the execution of this method, no methods are called from outside which change our // streams/partner instances. return(0); } }
/// <summary> /// Moves to the next available input source. /// </summary> /// <remarks> /// This method blocks the calling thread as long as the switching to the new input source lasts. This includes /// the crossfading duration (if crossfading is done) or the fading out (if no crossfading is done). /// </remarks> protected void MoveToNextInputSource_Sync() { // TODO: Insert gap between tracks if we are in playback mode Normal IInputSource inputSource = PeekNextInputSource(); if (_playbackSession != null) { BassCDTrackInputSource bcdtisNew = inputSource as BassCDTrackInputSource; IInputSource currentInputSource = _playbackSession.CurrentInputSource; BassCDTrackInputSource bcdtisOld = currentInputSource as BassCDTrackInputSource; if (bcdtisOld != null && bcdtisNew != null) { // Special treatment for CD drives: If the new input source is from the same audio CD drive, we must take the stream over if (bcdtisOld.SwitchTo(bcdtisNew)) { _playbackSession.IsAwaitingNextInputSource = false; ClearNextInputSource(); return; } } // TODO: Trigger crossfading if CF is configured _playbackSession.End(_internalState == InternalPlaybackState.Playing); // Only wait for fade out when we are playing } _internalState = InternalPlaybackState.Playing; if (inputSource == null) { Log.Debug("No more input sources available."); } else { Log.Debug("Playing next input source '{0}'", inputSource); } if (_playbackSession != null) { if (_playbackSession.InitializeWithNewInputSource(inputSource)) { _playbackSession.Play(); ClearNextInputSource(); return; } _playbackSession.Dispose(); _playbackSession = null; } if (inputSource == null) { Ended(); return; } _playbackSession = PlaybackSession.Create(_controller); if (_playbackSession == null) { _internalState = InternalPlaybackState.Stopped; return; } _playbackSession.Play(); _internalState = InternalPlaybackState.Playing; _controller.StateReady(); }