From bbbbc8e18879a5495be1cff78ca78be7a44ac25b Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Wed, 21 Apr 2021 13:46:57 +0300 Subject: iOS: Background audio; MP3/AAC playback --- src/audio/player.c | 87 +++++++++++++++++++++++++++++---- src/audio/player.h | 30 ++++++------ src/ios.h | 16 ++++++ src/ios.m | 127 ++++++++++++++++++++++++++++++++++++++++++++++++ src/media.c | 6 +-- src/ui/documentwidget.c | 3 ++ src/ui/window.c | 1 + 7 files changed, 242 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/audio/player.c b/src/audio/player.c index 35786284..20b40f73 100644 --- a/src/audio/player.c +++ b/src/audio/player.c @@ -33,11 +33,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #if defined (LAGRANGE_ENABLE_MPG123) -# include +# include +#endif +#if defined (iPlatformAppleMobile) +# include "../ios.h" #endif /*----------------------------------------------------------------------------------------------*/ +iDeclareType(AVFAudioPlayer) /* iOS */ + iDeclareType(ContentSpec) enum iDecoderType { @@ -447,6 +452,7 @@ struct Impl_Player { iInputBuf * data; uint32_t lastInteraction; iDecoder * decoder; + iAVFAudioPlayer * avfPlayer; /* iOS */ }; iDefineTypeConstruction(Player) @@ -634,24 +640,40 @@ static void writeOutputSamples_Player_(void *plr, Uint8 *stream, int len) { void init_Player(iPlayer *d) { iZap(d->spec); init_String(&d->mime); - d->device = 0; - d->decoder = NULL; - d->data = new_InputBuf(); - d->volume = 1.0f; - d->flags = 0; + d->device = 0; + d->decoder = NULL; + d->avfPlayer = NULL; + d->data = new_InputBuf(); + d->volume = 1.0f; + d->flags = 0; } void deinit_Player(iPlayer *d) { stop_Player(d); delete_InputBuf(d->data); deinit_String(&d->mime); +#if defined (iPlatformAppleMobile) + if (d->avfPlayer) { + delete_AVFAudioPlayer(d->avfPlayer); + } +#endif } iBool isStarted_Player(const iPlayer *d) { +#if defined (iPlatformAppleMobile) + if (d->avfPlayer) { + return isStarted_AVFAudioPlayer(d->avfPlayer); + } +#endif return d->device != 0; } iBool isPaused_Player(const iPlayer *d) { +#if defined (iPlatformAppleMobile) + if (d->avfPlayer) { + return isPaused_AVFAudioPlayer(d->avfPlayer); + } +#endif if (!d->device) return iTrue; return SDL_GetAudioDeviceStatus(d->device) == SDL_AUDIO_PAUSED; } @@ -676,15 +698,26 @@ void updateSourceData_Player(iPlayer *d, const iString *mimeType, const iBlock * case append_PlayerUpdate: { const size_t oldSize = size_Block(&input->data); const size_t newSize = size_Block(data); - iAssert(newSize >= oldSize); + if (input->isComplete) { + iAssert(newSize == oldSize); + break; + } /* The old parts cannot have changed. */ -// iAssert(memcmp(constData_Block(&input->data), constData_Block(data), oldSize) == 0); appendData_Block(&input->data, constBegin_Block(data) + oldSize, newSize - oldSize); - input->isComplete = iFalse; break; } case complete_PlayerUpdate: - input->isComplete = iTrue; + if (!input->isComplete) { + input->isComplete = iTrue; +#if defined (iPlatformAppleMobile) + iAssert(d->avfPlayer == NULL); + d->avfPlayer = new_AVFAudioPlayer(); + if (!setInput_AVFAudioPlayer(d->avfPlayer, &d->mime, &input->data)) { + delete_AVFAudioPlayer(d->avfPlayer); + d->avfPlayer = NULL; + } +#endif + } break; } signal_Condition(&input->changed); @@ -695,6 +728,13 @@ iBool start_Player(iPlayer *d) { if (isStarted_Player(d)) { return iFalse; } +#if defined (iPlatformAppleMobile) + if (d->avfPlayer) { + play_AVFAudioPlayer(d->avfPlayer); + setNotIdle_Player(d); + return iTrue; + } +#endif iContentSpec content = contentSpec_Player_(d); if (!content.output.freq) { return iFalse; @@ -713,6 +753,12 @@ iBool start_Player(iPlayer *d) { } void setPaused_Player(iPlayer *d, iBool isPaused) { +#if defined (iPlatformAppleMobile) + if (d->avfPlayer) { + setPaused_AVFAudioPlayer(d->avfPlayer, isPaused); + return; + } +#endif if (isStarted_Player(d)) { SDL_PauseAudioDevice(d->device, isPaused ? SDL_TRUE : SDL_FALSE); setNotIdle_Player(d); @@ -720,6 +766,12 @@ void setPaused_Player(iPlayer *d, iBool isPaused) { } void stop_Player(iPlayer *d) { +#if defined (iPlatformAppleMobile) + if (d->avfPlayer) { + stop_AVFAudioPlayer(d->avfPlayer); + return; + } +#endif if (isStarted_Player(d)) { /* TODO: Stop the stream/decoder. */ SDL_PauseAudioDevice(d->device, SDL_TRUE); @@ -735,6 +787,11 @@ void setVolume_Player(iPlayer *d, float volume) { if (d->decoder) { d->decoder->gain = d->volume; } +#if defined (iPlatformAppleMobile) + if (d->avfPlayer) { + setVolume_AVFAudioPlayer(d->avfPlayer, volume); + } +#endif setNotIdle_Player(d); } @@ -762,11 +819,21 @@ const iString *tag_Player(const iPlayer *d, enum iPlayerTag tag) { } float time_Player(const iPlayer *d) { +#if defined (iPlatformAppleMobile) + if (d->avfPlayer) { + return currentTime_AVFAudioPlayer(d->avfPlayer); + } +#endif if (!d->decoder) return 0; return (float) ((double) d->decoder->currentSample / (double) d->spec.freq); } float duration_Player(const iPlayer *d) { +#if defined (iPlatformAppleMobile) + if (d->avfPlayer) { + return duration_AVFAudioPlayer(d->avfPlayer); + } +#endif if (!d->decoder) return 0; return (float) ((double) d->decoder->totalSamples / (double) d->spec.freq); } diff --git a/src/audio/player.h b/src/audio/player.h index 82d95fd2..8753d811 100644 --- a/src/audio/player.h +++ b/src/audio/player.h @@ -49,21 +49,21 @@ enum iPlayerTag { void updateSourceData_Player (iPlayer *, const iString *mimeType, const iBlock *data, enum iPlayerUpdate update); -iBool start_Player (iPlayer *); -void stop_Player (iPlayer *); -void setPaused_Player (iPlayer *, iBool isPaused); -void setVolume_Player (iPlayer *, float volume); -void setFlags_Player (iPlayer *, int flags, iBool set); -void setNotIdle_Player (iPlayer *); - -int flags_Player (const iPlayer *); -const iString *tag_Player (const iPlayer *, enum iPlayerTag tag); -iBool isStarted_Player (const iPlayer *); -iBool isPaused_Player (const iPlayer *); -float volume_Player (const iPlayer *); -float time_Player (const iPlayer *); -float duration_Player (const iPlayer *); -float streamProgress_Player (const iPlayer *); /* normalized 0...1 */ +iBool start_Player (iPlayer *); +void stop_Player (iPlayer *); +void setPaused_Player (iPlayer *, iBool isPaused); +void setVolume_Player (iPlayer *, float volume); +void setFlags_Player (iPlayer *, int flags, iBool set); +void setNotIdle_Player (iPlayer *); + +int flags_Player (const iPlayer *); +const iString *tag_Player (const iPlayer *, enum iPlayerTag tag); +iBool isStarted_Player (const iPlayer *); +iBool isPaused_Player (const iPlayer *); +float volume_Player (const iPlayer *); +float time_Player (const iPlayer *); +float duration_Player (const iPlayer *); +float streamProgress_Player (const iPlayer *); /* normalized 0...1 */ uint32_t idleTimeMs_Player (const iPlayer *); iString * metadataLabel_Player (const iPlayer *); diff --git a/src/ios.h b/src/ios.h index ba462834..578c85fe 100644 --- a/src/ios.h +++ b/src/ios.h @@ -39,3 +39,19 @@ void exportDownloadedFile_iOS(const iString *path); iBool isPhone_iOS (void); void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom); int displayRefreshRate_iOS (void); + +/*----------------------------------------------------------------------------------------------*/ + +iDeclareType(AVFAudioPlayer) +iDeclareTypeConstruction(AVFAudioPlayer) + +iBool setInput_AVFAudioPlayer (iAVFAudioPlayer *, const iString *mediaType, const iBlock *audioFileData); +void play_AVFAudioPlayer (iAVFAudioPlayer *); +void stop_AVFAudioPlayer (iAVFAudioPlayer *); +void setPaused_AVFAudioPlayer (iAVFAudioPlayer *, iBool paused); +void setVolume_AVFAudioPlayer (iAVFAudioPlayer *, float volume); + +double currentTime_AVFAudioPlayer (const iAVFAudioPlayer *); +double duration_AVFAudioPlayer (const iAVFAudioPlayer *); +iBool isStarted_AVFAudioPlayer (const iAVFAudioPlayer *); +iBool isPaused_AVFAudioPlayer (const iAVFAudioPlayer *); diff --git a/src/ios.m b/src/ios.m index 58557ef5..ccf395fb 100644 --- a/src/ios.m +++ b/src/ios.m @@ -24,11 +24,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "app.h" #include "ui/command.h" #include "ui/window.h" + +#include +#include +#include #include #include +#include #import #import +#import static iBool isSystemDarkMode_ = iFalse; static iBool isPhone_ = iFalse; @@ -223,6 +229,7 @@ void setupApplication_iOS(void) { selector:@selector(keyboardOffScreen:) name:UIKeyboardWillHideNotification object:nil]; + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; } static iBool isDarkMode_(iWindow *window) { @@ -309,3 +316,123 @@ void exportDownloadedFile_iOS(const iString *path) { [appState_ setFileBeingSaved:path]; [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil]; } + +/*----------------------------------------------------------------------------------------------*/ + +enum iAVFAudioPlayerState { + initialized_AVFAudioPlayerState, + playing_AVFAudioPlayerState, + paused_AVFAudioPlayerState +}; + +struct Impl_AVFAudioPlayer { + iString cacheFilePath; + AVAudioPlayer *player; + float volume; + enum iAVFAudioPlayerState state; +}; + +iDefineTypeConstruction(AVFAudioPlayer) + +void init_AVFAudioPlayer(iAVFAudioPlayer *d) { + init_String(&d->cacheFilePath); + d->player = NULL; + d->volume = 1.0f; + d->state = initialized_AVFAudioPlayerState; +} + +void deinit_AVFAudioPlayer(iAVFAudioPlayer *d) { + setInput_AVFAudioPlayer(d, NULL, NULL); +} + +static const char *cacheDir_ = "~/Library/Caches/Audio"; + +static const char *fileExt_(const iString *mimeType) { + /* Media types that AVFAudioPlayer will try to play. */ + if (startsWithCase_String(mimeType, "audio/aiff") || + startsWithCase_String(mimeType, "audio/x-aiff")) { + return ".aiff"; + } + if (startsWithCase_String(mimeType, "audio/3gpp")) return ".3gpp"; + if (startsWithCase_String(mimeType, "audio/mpeg")) return ".mp3"; + if (startsWithCase_String(mimeType, "audio/mp3")) return ".mp3"; + if (startsWithCase_String(mimeType, "audio/mp4")) return ".mp4"; + if (startsWithCase_String(mimeType, "audio/mpeg4")) return ".mp4"; + if (startsWithCase_String(mimeType, "audio/aac")) return ".aac"; + return ""; +} + +iBool setInput_AVFAudioPlayer(iAVFAudioPlayer *d, const iString *mimeType, const iBlock *audioFileData) { + if (!isEmpty_String(&d->cacheFilePath)) { + remove(cstr_String(&d->cacheFilePath)); + clear_String(&d->cacheFilePath); + } + if (d->player) { + d->player = nil; + } + if (mimeType && audioFileData && iCmpStr(fileExt_(mimeType), "")) { + makeDirs_Path(collectNewCStr_String(cacheDir_)); + iFile *f = new_File(collectNewFormat_String("%s/%u%s", cacheDir_, SDL_GetTicks(), fileExt_(mimeType))); + if (open_File(f, writeOnly_FileMode)) { + write_File(f, audioFileData); + set_String(&d->cacheFilePath, path_File(f)); + NSError *error = nil; + d->player = [[AVAudioPlayer alloc] + initWithContentsOfURL:[NSURL fileURLWithPath: + [NSString stringWithUTF8String:cstr_String(&d->cacheFilePath)]] + error:&error]; + if (error) { + d->player = nil; + } + [d->player setVolume:d->volume]; + } + iRelease(f); + } + return d->player != nil; +} + +void play_AVFAudioPlayer(iAVFAudioPlayer *d) { + if (d->state != playing_AVFAudioPlayerState) { + [d->player play]; + d->state = playing_AVFAudioPlayerState; + } +} + +void stop_AVFAudioPlayer(iAVFAudioPlayer *d) { + [d->player stop]; + d->state = initialized_AVFAudioPlayerState; +} + +void setPaused_AVFAudioPlayer(iAVFAudioPlayer *d, iBool paused) { + if (paused && d->state != paused_AVFAudioPlayerState) { + [d->player pause]; + d->state = paused_AVFAudioPlayerState; + } + else if (!paused && d->state != playing_AVFAudioPlayerState) { + [d->player play]; + d->state = playing_AVFAudioPlayerState; + } +} + +void setVolume_AVFAudioPlayer(iAVFAudioPlayer *d, float volume) { + d->volume = volume; + if (d->player) { + [d->player setVolume:volume]; + } +} + +double currentTime_AVFAudioPlayer(const iAVFAudioPlayer *d) { + return [d->player currentTime]; +} + +double duration_AVFAudioPlayer(const iAVFAudioPlayer *d) { + return [d->player duration]; +} + +iBool isStarted_AVFAudioPlayer(const iAVFAudioPlayer *d) { + return d->state != initialized_AVFAudioPlayerState; +} + +iBool isPaused_AVFAudioPlayer(const iAVFAudioPlayer *d) { + return d->state == paused_AVFAudioPlayerState; +} diff --git a/src/media.c b/src/media.c index c3b38ae3..1313b7da 100644 --- a/src/media.c +++ b/src/media.c @@ -312,13 +312,13 @@ iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlo audio = at_PtrArray(&d->audio, existing - 1); iAssert(equal_String(&audio->props.mime, mime)); /* MIME cannot change */ updateSourceData_Player(audio->player, mime, data, append_PlayerUpdate); + if (!isPartial) { + updateSourceData_Player(audio->player, NULL, NULL, complete_PlayerUpdate); + } if (!isStarted_Player(audio->player)) { /* Maybe the previous updates didn't have enough data. */ start_Player(audio->player); } - if (!isPartial) { - updateSourceData_Player(audio->player, NULL, NULL, complete_PlayerUpdate); - } } } else if ((existing = findLinkDownload_Media(d, linkId)) != 0) { diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index d847c19f..be831829 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c @@ -710,6 +710,9 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) { if (document_App() != d) { return 0; } + if (get_Window()->isDrawFrozen) { + return 0; + } static const uint32_t invalidInterval_ = ~0u; uint32_t interval = invalidInterval_; iConstForEach(PtrArray, i, &d->visibleMedia) { diff --git a/src/ui/window.c b/src/ui/window.c index 73590d7f..b400dced 100644 --- a/src/ui/window.c +++ b/src/ui/window.c @@ -1887,6 +1887,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { SDL_ShowWindow(d->win); } postRefresh_App(); + postCommand_App("media.player.update"); /* in case a player needs updating */ return iTrue; } if (processEvent_Touch(&event)) { -- cgit v1.2.3