/// <summary> /// Plays the playlist from the current item index. /// </summary> public void Play(double initialPosition, bool startPaused) { try { if (_isPlaying) { if (_currentLoop != null) { //Tracing.Log("Player.Play -- Stopping current loop..."); StopLoop(); } //Tracing.Log("Player.Play -- Stopping playback..."); Stop(); } RemoveSyncCallbacks(); _currentLoop = null; _positionOffset = 0; _currentMixPlaylistIndex = Playlist.CurrentItemIndex; // How many channels are left? int channelsToLoad = Playlist.Items.Count - Playlist.CurrentItemIndex; // If there are more than 2, just limit to 2 for now. The other channels are loaded dynamically. if (channelsToLoad > 2) channelsToLoad = 2; // Check for channels to load if (channelsToLoad == 0) throw new Exception("Error in Player.Play: There aren't any channels to play!"); // Load the current channel and the next channel if it exists for (int a = Playlist.CurrentItemIndex; a < Playlist.CurrentItemIndex + channelsToLoad; a++) _playlist.Items[a].Load(_useFloatingPoint); // Start decoding first playlist item //_decodingService.StartDecodingFile(_playlist.Items[0].AudioFile.FilePath, _positionOffset); try { // Create the streaming channel (set the frequency to the first file in the list) //Tracing.Log("Player.Play -- Creating streaming channel (SampleRate: " + _playlist.CurrentItem.AudioFile.SampleRate + " Hz, FloatingPoint: true)..."); #if IOS _streamProc = new STREAMPROC(StreamCallbackIOS); #else _streamProc = new STREAMPROC(StreamCallback); #endif _streamChannel = Channel.CreateStream(_playlist.CurrentItem.AudioFile.SampleRate, 2, _useFloatingPoint, _streamProc); //Tracing.Log("Player.Play -- Creating time shifting channel..."); _fxChannel = Channel.CreateStreamForTimeShifting(_streamChannel.Handle, true, _useFloatingPoint); //_fxChannel = Channel.CreateStreamForTimeShifting(_streamChannel.Handle, false, _useFloatingPoint); //_fxChannel = _streamChannel; } catch(Exception ex) { // Raise custom exception with information (so the client application can maybe deactivate floating point for example) PlayerCreateStreamException newEx = new PlayerCreateStreamException("The player has failed to create the stream channel (" + ex.Message + ").", ex); newEx.Decode = true; newEx.UseFloatingPoint = true; newEx.SampleRate = _playlist.CurrentItem.AudioFile.SampleRate; throw newEx; } // Check driver type if (_device.DriverType == DriverType.DirectSound) { try { // Create mixer stream Tracing.Log("Player.Play -- Creating mixer channel (DirectSound)..."); _mixerChannel = MixerChannel.CreateMixerStream(_playlist.CurrentItem.AudioFile.SampleRate, 2, _useFloatingPoint, false); _mixerChannel.AddChannel(_fxChannel.Handle); //_mixerChannel = _fxChannel; AddBPMCallbacks(); } catch (Exception ex) { // Raise custom exception with information (so the client application can maybe deactivate floating point for example) PlayerCreateStreamException newEx = new PlayerCreateStreamException("The player has failed to create the time shifting channel.", ex); newEx.UseFloatingPoint = true; newEx.UseTimeShifting = true; newEx.SampleRate = _playlist.CurrentItem.AudioFile.SampleRate; throw newEx; } } #if !IOS && !ANDROID else if (_device.DriverType == DriverType.ASIO) { try { // Create mixer stream Tracing.Log("Player.Play -- Creating mixer channel (ASIO)..."); _mixerChannel = MixerChannel.CreateMixerStream(_playlist.CurrentItem.AudioFile.SampleRate, 2, _useFloatingPoint, true); _mixerChannel.AddChannel(_fxChannel.Handle); } catch (Exception ex) { // Raise custom exception with information (so the client application can maybe deactivate floating point for example) PlayerCreateStreamException newEx = new PlayerCreateStreamException("The player has failed to create the time shifting channel.", ex); newEx.DriverType = DriverType.ASIO; newEx.UseFloatingPoint = true; newEx.UseTimeShifting = true; newEx.Decode = true; newEx.SampleRate = _playlist.CurrentItem.AudioFile.SampleRate; throw newEx; } // Set floating point BassAsio.BASS_ASIO_ChannelSetFormat(false, 0, BASSASIOFormat.BASS_ASIO_FORMAT_FLOAT); // Create callback asioProc = new ASIOPROC(AsioCallback); try { // Enable and join channels (for stereo output Tracing.Log("Player.Play -- Enabling ASIO channels..."); BassAsio.BASS_ASIO_ChannelEnable(false, 0, asioProc, new IntPtr(_mixerChannel.Handle)); Tracing.Log("Player.Play -- Joining ASIO channels..."); BassAsio.BASS_ASIO_ChannelJoin(false, 1, 0); // Set sample rate Tracing.Log("Player.Play -- Set ASIO sample rates..."); BassAsio.BASS_ASIO_ChannelSetRate(false, 0, _playlist.CurrentItem.AudioFile.SampleRate); BassAsio.BASS_ASIO_SetRate(_playlist.CurrentItem.AudioFile.SampleRate); } catch (Exception ex) { // Raise custom exception with information (so the client application can maybe deactivate floating point for example) PlayerCreateStreamException newEx = new PlayerCreateStreamException("The player has failed to enable or join ASIO channels.", ex); newEx.DriverType = DriverType.ASIO; newEx.SampleRate = _playlist.CurrentItem.AudioFile.SampleRate; throw newEx; } // Start playback Tracing.Log("Player.Play -- Starting ASIO buffering..."); if (!BassAsio.BASS_ASIO_Start(0)) { // Get BASS error BASSError error = Bass.BASS_ErrorGetCode(); // Raise custom exception with information (so the client application can maybe deactivate floating point for example) PlayerCreateStreamException newEx = new PlayerCreateStreamException("The player has failed to create the ASIO channel (" + error.ToString() + ").", null); newEx.DriverType = DriverType.ASIO; newEx.UseFloatingPoint = true; newEx.UseTimeShifting = true; newEx.Decode = true; newEx.SampleRate = _playlist.CurrentItem.AudioFile.SampleRate; throw newEx; } } else if (_device.DriverType == DriverType.WASAPI) { // Create mixer stream Tracing.Log("Player.Play -- Creating mixer channel (WASAPI)..."); _mixerChannel = MixerChannel.CreateMixerStream(_playlist.CurrentItem.AudioFile.SampleRate, 2, true, true); _mixerChannel.AddChannel(_fxChannel.Handle); // Start playback if (!BassWasapi.BASS_WASAPI_Start()) { // Get error BASSError error = Bass.BASS_ErrorGetCode(); throw new Exception("Player.Play error: Error playing files in WASAPI: " + error.ToString()); } } #endif // Set initial volume _mixerChannel.Volume = Volume; // Load 18-band equalizer //Tracing.Log("Player.Play -- Creating equalizer (Preset: " + _currentEQPreset + ")..."); AddEQ(_currentEQPreset); // Check if EQ is bypassed if (_isEQBypassed) { // Reset EQ //Tracing.Log("Player.Play -- Equalizer is bypassed; resetting EQ..."); ResetEQ(); } // Check if the song must be looped if (_repeatType == RepeatType.Song) _playlist.CurrentItem.Channel.SetFlags(BASSFlag.BASS_SAMPLE_LOOP, BASSFlag.BASS_SAMPLE_LOOP); long length = _playlist.CurrentItem.Channel.GetLength(); SetSyncCallback(length); _isPlaying = true; // Only the DirectSound mode needs to start the main channel since it's not in decode mode. if (_device.DriverType == DriverType.DirectSound) { // For iOS: This is required to update the AirPlay/remote player status Base.Start(); // Start playback //Tracing.Log("Player.Play -- Starting DirectSound playback..."); _mixerChannel.Play(false); if (startPaused) { if(initialPosition > 0) SetPosition(initialPosition); Base.Pause(); } _isPaused = startPaused; } // StartEncode(EncoderType.OGG); // StartCast(new CastServerParams() // { // Bitrate = 128, // Name = "Sessions Test Server", // Url = "localhost:8000", // Password = "******" // }); // Raise audio file finished event (if an event is subscribed) if (OnPlaylistIndexChanged != null) { PlayerPlaylistIndexChangedData data = new PlayerPlaylistIndexChangedData(); data.IsPlaybackStopped = false; data.AudioFileStarted = _playlist.CurrentItem.AudioFile; data.PlaylistName = "New playlist 1"; data.PlaylistCount = _playlist.Items.Count; data.PlaylistIndex = _playlist.CurrentItemIndex; if (Playlist.CurrentItemIndex < Playlist.Items.Count - 2) data.NextAudioFile = Playlist.Items[Playlist.CurrentItemIndex + 1].AudioFile; OnPlaylistIndexChanged(data); } } catch (Exception ex) { Tracing.Log("Player.Play error: " + ex.Message + "\n" + ex.StackTrace); throw; } }
/// <summary> /// Sync callback routine used for triggering the playlist index changed event and /// changing the output stream sample rate if necessary. /// </summary> /// <param name="handle">Handle to the sync</param> /// <param name="channel">Channel handle</param> /// <param name="data">Data</param> /// <param name="user">User data</param> internal void PlayerSyncProc(int handle, int channel, int data, IntPtr user) { bool playbackStopped = false; bool playlistBackToStart = false; int nextPlaylistIndex = 0; _mixerChannel.Lock(true); long position = _mixerChannel.GetPosition(); // if (_useFloatingPoint) // position /= 2; // Get remanining data in buffer //int buffered = mainChannel.GetData(IntPtr.Zero, (int)BASSData.BASS_DATA_AVAILABLE); // Check if this the last song if (_playlist.CurrentItemIndex == _playlist.Items.Count - 1) { // This is the end of the playlist. Check the repeat type if the playlist needs to be repeated if (RepeatType == RepeatType.Playlist) { // Set flags nextPlaylistIndex = 0; playlistBackToStart = true; } else { // Set flags playbackStopped = true; } } else { // Set flags nextPlaylistIndex = _playlist.CurrentItemIndex + 1; } // Calculate position offset long offset = 0 - (position / 2); if (!playbackStopped) { // Multiply by 1.5 (I don't really know why, but this works for 48000Hz and 96000Hz. Maybe a bug in BASS with FLAC files?) if (Playlist.CurrentItem.AudioFile.FileType == AudioFileFormat.FLAC && Playlist.Items[nextPlaylistIndex].AudioFile.SampleRate > 44100) offset = (long)((float)offset * 1.5f); // Check if the sample rate needs to be changed (i.e. main channel sample rate different than the decoding file) if (!playbackStopped && _mixerChannel.SampleRate != Playlist.Items[nextPlaylistIndex].AudioFile.SampleRate) _mixerChannel.SetSampleRate(Playlist.Items[nextPlaylistIndex].AudioFile.SampleRate); // Set position offset _positionOffset = offset; } _mixerChannel.Lock(false); RemoveSyncCallback(handle); // Check if this is the last item to play if (_playlist.CurrentItemIndex == _playlist.Items.Count - 1) { // This is the end of the playlist. Check the repeat type if the playlist needs to be repeated if (RepeatType == RepeatType.Playlist) Playlist.First(); } else { // Increment playlist index Playlist.Next(); } // Check if an event is subscribed if (OnPlaylistIndexChanged != null) { // Create data PlayerPlaylistIndexChangedData eventData = new PlayerPlaylistIndexChangedData(); eventData.IsPlaybackStopped = playbackStopped; eventData.PlaylistName = "New playlist 1"; eventData.PlaylistCount = _playlist.Items.Count; eventData.PlaylistIndex = _playlist.CurrentItemIndex; // If the playback hasn't stopped, fill more event data if (playbackStopped) { // Set event data eventData.AudioFileStarted = null; eventData.AudioFileEnded = Playlist.CurrentItem.AudioFile; // Check if EQ is enabled if (_isEQEnabled) RemoveEQ(); // Dispose channels _playlist.DisposeChannels(); _isPlaying = false; } else { // Set event data eventData.AudioFileStarted = Playlist.CurrentItem.AudioFile; if (Playlist.CurrentItemIndex < Playlist.Items.Count - 2) eventData.NextAudioFile = Playlist.Items[Playlist.CurrentItemIndex + 1].AudioFile; // Is this the first item, and did the last song of the playlist just play? if (Playlist.CurrentItemIndex == 0 && playlistBackToStart) { // The audio file that just finished was the last of the playlist eventData.AudioFileEnded = Playlist.Items[Playlist.Items.Count - 1].AudioFile; } // Make sure this is not the first item else if (Playlist.CurrentItemIndex > 0) { // The audio file that just finished was the last one eventData.AudioFileEnded = Playlist.Items[Playlist.CurrentItemIndex - 1].AudioFile; } } // Raise event OnPlaylistIndexChanged(eventData); } }