diff --git a/README.md b/README.md index 94889a1..b89be77 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ example with curl: ``` curl -X PUT -H "Content-Type: application/json" -d "{\"file_id\": \".wav\", \"start_offset_ms\":0}" "http://127.0.0.1:8080/api/current-song" ``` +`start_offset_ms` can be negative, in which case song will start to play in the future. To stop an audio file which is currently playing, send a json to uri http://YOUR_IP:HTTP_LISTEN_PORT/api/current-song with empty or missing 'file_id': `{ "file_id": "" }` or `{}` diff --git a/src/current_song_controller.cc b/src/current_song_controller.cc index 5a22428..d9ce637 100644 --- a/src/current_song_controller.cc +++ b/src/current_song_controller.cc @@ -57,7 +57,7 @@ namespace wavplayeralsa bool CurrentSongController::NewSongRequest( const std::string &file_id, - uint64_t start_offset_ms, + int64_t start_offset_ms, std::stringstream &out_msg, uint32_t *play_seq_id) { @@ -101,7 +101,7 @@ namespace wavplayeralsa } else { static const int SECONDS_PER_HOUR = (60 * 60); - uint64_t start_offset_sec = start_offset_ms / 1000; + uint64_t start_offset_sec = std::abs(start_offset_ms) / 1000; uint64_t hours = start_offset_sec / SECONDS_PER_HOUR; start_offset_sec = start_offset_sec - hours * SECONDS_PER_HOUR; uint64_t minutes = start_offset_sec / 60; @@ -115,7 +115,11 @@ namespace wavplayeralsa out_msg << "starting at position " << start_offset_ms << " ms " << "(" << hours << ":" << std::setfill('0') << std::setw(2) << minutes << ":" << - std::setfill('0') << std::setw(2) << seconds << ")"; + std::setfill('0') << std::setw(2) << seconds; + if(start_offset_ms < 0) { + out_msg << " in the future"; + } + out_msg << ")"; } try { diff --git a/src/current_song_controller.h b/src/current_song_controller.h index 73ecb43..f0a7057 100644 --- a/src/current_song_controller.h +++ b/src/current_song_controller.h @@ -39,7 +39,7 @@ namespace wavplayeralsa { bool NewSongRequest( const std::string &file_id, - uint64_t start_offset_ms, + int64_t start_offset_ms, std::stringstream &out_msg, uint32_t *play_seq_id); diff --git a/src/http_api.cc b/src/http_api.cc index ca5ef17..3daac7a 100644 --- a/src/http_api.cc +++ b/src/http_api.cc @@ -106,11 +106,11 @@ namespace wavplayeralsa { } } - uint64_t start_offset_ms = 0; + int64_t start_offset_ms = 0; // use it only if it is found in the json if(request_json.find("start_offset_ms") != request_json.end()) { try { - start_offset_ms = request_json["start_offset_ms"].get(); + start_offset_ms = request_json["start_offset_ms"].get(); } catch(json::exception &e) { std::stringstream err_stream; diff --git a/src/player_actions_ifc.h b/src/player_actions_ifc.h index a2c447c..ad0b5bb 100644 --- a/src/player_actions_ifc.h +++ b/src/player_actions_ifc.h @@ -18,7 +18,7 @@ namespace wavplayeralsa { virtual bool NewSongRequest( const std::string &file_id, - uint64_t start_offset_ms, + int64_t start_offset_ms, std::stringstream &out_msg, uint32_t *play_seq_id) = 0; diff --git a/src/services/alsa_service.cc b/src/services/alsa_service.cc index 0a21baf..a276125 100644 --- a/src/services/alsa_service.cc +++ b/src/services/alsa_service.cc @@ -31,7 +31,7 @@ namespace wavplayeralsa ~AlsaPlaybackService(); public: - void Play(int32_t offset_in_ms); + void Play(int64_t offset_in_ms); bool Stop(); const std::string GetFileId() const { return file_id_; } @@ -66,7 +66,7 @@ namespace wavplayeralsa snd_pcm_t *alsa_playback_handle_ = nullptr; // what is the next frame to be delivered to alsa - uint64_t curr_position_frames_ = 0; + int64_t curr_position_frames_ = 0; // snd file private: @@ -395,8 +395,8 @@ namespace wavplayeralsa return false; } - void AlsaPlaybackService::Play(int32_t offset_in_ms) { - + void AlsaPlaybackService::Play(int64_t offset_in_ms) { + if(!initialized_) { throw std::runtime_error("tried to play wav file on an uninitialzed alsa service"); } @@ -406,11 +406,11 @@ namespace wavplayeralsa } double position_in_seconds = (double)offset_in_ms / 1000.0; - curr_position_frames_ = position_in_seconds * (double)frame_rate_; - if(curr_position_frames_ > total_frame_in_file_) { - curr_position_frames_ = total_frame_in_file_; + curr_position_frames_ = position_in_seconds * (double)frame_rate_; + curr_position_frames_ = std::min(curr_position_frames_, (int64_t)total_frame_in_file_); + if(curr_position_frames_ >= 0) { + sf_count_t seek_res = snd_file_.seek(curr_position_frames_, SEEK_SET); } - sf_count_t seek_res = snd_file_.seek(curr_position_frames_, SEEK_SET); logger_->info("start playing file {} from position {} mili-seconds ({} seconds)", file_id_, offset_in_ms, position_in_seconds); playing_thread_ = std::thread(&AlsaPlaybackService::PlayingThreadMain, this); @@ -473,19 +473,28 @@ namespace wavplayeralsa // we want to deliver as many frames as possible. // we can put frames_to_deliver number of frames, but the buffer can only hold frames_capacity_in_buffer_ frames frames_to_deliver = std::min(frames_to_deliver, frames_capacity_in_buffer_); - unsigned int bytes_to_deliver = frames_to_deliver * bytes_per_frame_; // read the frames from the file. TODO: what if readRaw fails? char buffer_for_transfer[TRANSFER_BUFFER_SIZE]; - bytes_to_deliver = snd_file_.readRaw(buffer_for_transfer, bytes_to_deliver); - if(bytes_to_deliver < 0) { - err_desc << "Failed reading raw frames from snd file. returned: " << sf_error_number(bytes_to_deliver); - throw std::runtime_error(err_desc.str()); + + bool start_in_future = (curr_position_frames_ < 0); + if(!start_in_future) { + unsigned int bytes_to_deliver = frames_to_deliver * bytes_per_frame_; + bytes_to_deliver = snd_file_.readRaw(buffer_for_transfer, bytes_to_deliver); + if(bytes_to_deliver < 0) { + err_desc << "Failed reading raw frames from snd file. returned: " << sf_error_number(bytes_to_deliver); + throw std::runtime_error(err_desc.str()); + } + if(bytes_to_deliver == 0) { + logger_->info("play_seq_id: {}. done writing all frames to pcm. waiting for audio device to play remaining frames in the buffer", play_seq_id_); + ios_.post(std::bind(&AlsaPlaybackService::PcmDrainLoop, this, boost::system::error_code())); + return; + } } - if(bytes_to_deliver == 0) { - logger_->info("play_seq_id: {}. done writing all frames to pcm. waiting for audio device to play remaining frames in the buffer", play_seq_id_); - ios_.post(std::bind(&AlsaPlaybackService::PcmDrainLoop, this, boost::system::error_code())); - return; + else { + frames_to_deliver = std::min(frames_to_deliver, (snd_pcm_sframes_t)-curr_position_frames_); + unsigned int bytes_to_deliver = frames_to_deliver * bytes_per_frame_; + bzero(buffer_for_transfer, bytes_to_deliver); } int frames_written = snd_pcm_writei(alsa_playback_handle_, buffer_for_transfer, frames_to_deliver); @@ -495,7 +504,7 @@ namespace wavplayeralsa } curr_position_frames_ += frames_written; - if(frames_written != frames_to_deliver) { + if( (curr_position_frames_ >= 0) && (start_in_future || (frames_written != frames_to_deliver))) { logger_->warn("play_seq_id: {}. transfered to alsa less frame then requested. frames_to_deliver: {}, frames_written: {}", play_seq_id_, frames_to_deliver, frames_written); snd_file_.seek(curr_position_frames_, SEEK_SET); } @@ -536,7 +545,6 @@ namespace wavplayeralsa void AlsaPlaybackService::CheckSongStartTime() { int err; snd_pcm_sframes_t delay = 0; - int64_t pos_in_frames = 0; if( (err = snd_pcm_delay(alsa_playback_handle_, &delay)) < 0) { std::stringstream err_desc; @@ -548,7 +556,7 @@ namespace wavplayeralsa if(delay < 4096) { return; } - pos_in_frames = curr_position_frames_ - delay; + int64_t pos_in_frames = curr_position_frames_ - delay; int64_t ms_since_audio_file_start = ((pos_in_frames * (int64_t)1000) / (int64_t)frame_rate_); struct timeval tv; diff --git a/src/services/alsa_service.h b/src/services/alsa_service.h index b2b4297..7e6b763 100644 --- a/src/services/alsa_service.h +++ b/src/services/alsa_service.h @@ -16,7 +16,7 @@ namespace wavplayeralsa public: virtual const std::string GetFileId() const = 0; - virtual void Play(int32_t offset_in_ms) = 0; + virtual void Play(int64_t offset_in_ms) = 0; virtual bool Stop() = 0; };