summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-04-21 13:46:57 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-04-21 13:46:57 +0300
commitbbbbc8e18879a5495be1cff78ca78be7a44ac25b (patch)
treecf24ab2e663e6a70250346328825f9c4a4eff45a /src
parent8b12e6f56fddc4a10e74591ebf6d9af38a9f0bb5 (diff)
iOS: Background audio; MP3/AAC playback
Diffstat (limited to 'src')
-rw-r--r--src/audio/player.c87
-rw-r--r--src/audio/player.h30
-rw-r--r--src/ios.h16
-rw-r--r--src/ios.m127
-rw-r--r--src/media.c6
-rw-r--r--src/ui/documentwidget.c3
-rw-r--r--src/ui/window.c1
7 files changed, 242 insertions, 28 deletions
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. */
33#include <SDL_timer.h> 33#include <SDL_timer.h>
34 34
35#if defined (LAGRANGE_ENABLE_MPG123) 35#if defined (LAGRANGE_ENABLE_MPG123)
36# include <mpg123.h> 36# include <mpg123.h>
37#endif
38#if defined (iPlatformAppleMobile)
39# include "../ios.h"
37#endif 40#endif
38 41
39/*----------------------------------------------------------------------------------------------*/ 42/*----------------------------------------------------------------------------------------------*/
40 43
44iDeclareType(AVFAudioPlayer) /* iOS */
45
41iDeclareType(ContentSpec) 46iDeclareType(ContentSpec)
42 47
43enum iDecoderType { 48enum iDecoderType {
@@ -447,6 +452,7 @@ struct Impl_Player {
447 iInputBuf * data; 452 iInputBuf * data;
448 uint32_t lastInteraction; 453 uint32_t lastInteraction;
449 iDecoder * decoder; 454 iDecoder * decoder;
455 iAVFAudioPlayer * avfPlayer; /* iOS */
450}; 456};
451 457
452iDefineTypeConstruction(Player) 458iDefineTypeConstruction(Player)
@@ -634,24 +640,40 @@ static void writeOutputSamples_Player_(void *plr, Uint8 *stream, int len) {
634void init_Player(iPlayer *d) { 640void init_Player(iPlayer *d) {
635 iZap(d->spec); 641 iZap(d->spec);
636 init_String(&d->mime); 642 init_String(&d->mime);
637 d->device = 0; 643 d->device = 0;
638 d->decoder = NULL; 644 d->decoder = NULL;
639 d->data = new_InputBuf(); 645 d->avfPlayer = NULL;
640 d->volume = 1.0f; 646 d->data = new_InputBuf();
641 d->flags = 0; 647 d->volume = 1.0f;
648 d->flags = 0;
642} 649}
643 650
644void deinit_Player(iPlayer *d) { 651void deinit_Player(iPlayer *d) {
645 stop_Player(d); 652 stop_Player(d);
646 delete_InputBuf(d->data); 653 delete_InputBuf(d->data);
647 deinit_String(&d->mime); 654 deinit_String(&d->mime);
655#if defined (iPlatformAppleMobile)
656 if (d->avfPlayer) {
657 delete_AVFAudioPlayer(d->avfPlayer);
658 }
659#endif
648} 660}
649 661
650iBool isStarted_Player(const iPlayer *d) { 662iBool isStarted_Player(const iPlayer *d) {
663#if defined (iPlatformAppleMobile)
664 if (d->avfPlayer) {
665 return isStarted_AVFAudioPlayer(d->avfPlayer);
666 }
667#endif
651 return d->device != 0; 668 return d->device != 0;
652} 669}
653 670
654iBool isPaused_Player(const iPlayer *d) { 671iBool isPaused_Player(const iPlayer *d) {
672#if defined (iPlatformAppleMobile)
673 if (d->avfPlayer) {
674 return isPaused_AVFAudioPlayer(d->avfPlayer);
675 }
676#endif
655 if (!d->device) return iTrue; 677 if (!d->device) return iTrue;
656 return SDL_GetAudioDeviceStatus(d->device) == SDL_AUDIO_PAUSED; 678 return SDL_GetAudioDeviceStatus(d->device) == SDL_AUDIO_PAUSED;
657} 679}
@@ -676,15 +698,26 @@ void updateSourceData_Player(iPlayer *d, const iString *mimeType, const iBlock *
676 case append_PlayerUpdate: { 698 case append_PlayerUpdate: {
677 const size_t oldSize = size_Block(&input->data); 699 const size_t oldSize = size_Block(&input->data);
678 const size_t newSize = size_Block(data); 700 const size_t newSize = size_Block(data);
679 iAssert(newSize >= oldSize); 701 if (input->isComplete) {
702 iAssert(newSize == oldSize);
703 break;
704 }
680 /* The old parts cannot have changed. */ 705 /* The old parts cannot have changed. */
681// iAssert(memcmp(constData_Block(&input->data), constData_Block(data), oldSize) == 0);
682 appendData_Block(&input->data, constBegin_Block(data) + oldSize, newSize - oldSize); 706 appendData_Block(&input->data, constBegin_Block(data) + oldSize, newSize - oldSize);
683 input->isComplete = iFalse;
684 break; 707 break;
685 } 708 }
686 case complete_PlayerUpdate: 709 case complete_PlayerUpdate:
687 input->isComplete = iTrue; 710 if (!input->isComplete) {
711 input->isComplete = iTrue;
712#if defined (iPlatformAppleMobile)
713 iAssert(d->avfPlayer == NULL);
714 d->avfPlayer = new_AVFAudioPlayer();
715 if (!setInput_AVFAudioPlayer(d->avfPlayer, &d->mime, &input->data)) {
716 delete_AVFAudioPlayer(d->avfPlayer);
717 d->avfPlayer = NULL;
718 }
719#endif
720 }
688 break; 721 break;
689 } 722 }
690 signal_Condition(&input->changed); 723 signal_Condition(&input->changed);
@@ -695,6 +728,13 @@ iBool start_Player(iPlayer *d) {
695 if (isStarted_Player(d)) { 728 if (isStarted_Player(d)) {
696 return iFalse; 729 return iFalse;
697 } 730 }
731#if defined (iPlatformAppleMobile)
732 if (d->avfPlayer) {
733 play_AVFAudioPlayer(d->avfPlayer);
734 setNotIdle_Player(d);
735 return iTrue;
736 }
737#endif
698 iContentSpec content = contentSpec_Player_(d); 738 iContentSpec content = contentSpec_Player_(d);
699 if (!content.output.freq) { 739 if (!content.output.freq) {
700 return iFalse; 740 return iFalse;
@@ -713,6 +753,12 @@ iBool start_Player(iPlayer *d) {
713} 753}
714 754
715void setPaused_Player(iPlayer *d, iBool isPaused) { 755void setPaused_Player(iPlayer *d, iBool isPaused) {
756#if defined (iPlatformAppleMobile)
757 if (d->avfPlayer) {
758 setPaused_AVFAudioPlayer(d->avfPlayer, isPaused);
759 return;
760 }
761#endif
716 if (isStarted_Player(d)) { 762 if (isStarted_Player(d)) {
717 SDL_PauseAudioDevice(d->device, isPaused ? SDL_TRUE : SDL_FALSE); 763 SDL_PauseAudioDevice(d->device, isPaused ? SDL_TRUE : SDL_FALSE);
718 setNotIdle_Player(d); 764 setNotIdle_Player(d);
@@ -720,6 +766,12 @@ void setPaused_Player(iPlayer *d, iBool isPaused) {
720} 766}
721 767
722void stop_Player(iPlayer *d) { 768void stop_Player(iPlayer *d) {
769#if defined (iPlatformAppleMobile)
770 if (d->avfPlayer) {
771 stop_AVFAudioPlayer(d->avfPlayer);
772 return;
773 }
774#endif
723 if (isStarted_Player(d)) { 775 if (isStarted_Player(d)) {
724 /* TODO: Stop the stream/decoder. */ 776 /* TODO: Stop the stream/decoder. */
725 SDL_PauseAudioDevice(d->device, SDL_TRUE); 777 SDL_PauseAudioDevice(d->device, SDL_TRUE);
@@ -735,6 +787,11 @@ void setVolume_Player(iPlayer *d, float volume) {
735 if (d->decoder) { 787 if (d->decoder) {
736 d->decoder->gain = d->volume; 788 d->decoder->gain = d->volume;
737 } 789 }
790#if defined (iPlatformAppleMobile)
791 if (d->avfPlayer) {
792 setVolume_AVFAudioPlayer(d->avfPlayer, volume);
793 }
794#endif
738 setNotIdle_Player(d); 795 setNotIdle_Player(d);
739} 796}
740 797
@@ -762,11 +819,21 @@ const iString *tag_Player(const iPlayer *d, enum iPlayerTag tag) {
762} 819}
763 820
764float time_Player(const iPlayer *d) { 821float time_Player(const iPlayer *d) {
822#if defined (iPlatformAppleMobile)
823 if (d->avfPlayer) {
824 return currentTime_AVFAudioPlayer(d->avfPlayer);
825 }
826#endif
765 if (!d->decoder) return 0; 827 if (!d->decoder) return 0;
766 return (float) ((double) d->decoder->currentSample / (double) d->spec.freq); 828 return (float) ((double) d->decoder->currentSample / (double) d->spec.freq);
767} 829}
768 830
769float duration_Player(const iPlayer *d) { 831float duration_Player(const iPlayer *d) {
832#if defined (iPlatformAppleMobile)
833 if (d->avfPlayer) {
834 return duration_AVFAudioPlayer(d->avfPlayer);
835 }
836#endif
770 if (!d->decoder) return 0; 837 if (!d->decoder) return 0;
771 return (float) ((double) d->decoder->totalSamples / (double) d->spec.freq); 838 return (float) ((double) d->decoder->totalSamples / (double) d->spec.freq);
772} 839}
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 {
49void updateSourceData_Player (iPlayer *, const iString *mimeType, const iBlock *data, 49void updateSourceData_Player (iPlayer *, const iString *mimeType, const iBlock *data,
50 enum iPlayerUpdate update); 50 enum iPlayerUpdate update);
51 51
52iBool start_Player (iPlayer *); 52iBool start_Player (iPlayer *);
53void stop_Player (iPlayer *); 53void stop_Player (iPlayer *);
54void setPaused_Player (iPlayer *, iBool isPaused); 54void setPaused_Player (iPlayer *, iBool isPaused);
55void setVolume_Player (iPlayer *, float volume); 55void setVolume_Player (iPlayer *, float volume);
56void setFlags_Player (iPlayer *, int flags, iBool set); 56void setFlags_Player (iPlayer *, int flags, iBool set);
57void setNotIdle_Player (iPlayer *); 57void setNotIdle_Player (iPlayer *);
58 58
59int flags_Player (const iPlayer *); 59int flags_Player (const iPlayer *);
60const iString *tag_Player (const iPlayer *, enum iPlayerTag tag); 60const iString *tag_Player (const iPlayer *, enum iPlayerTag tag);
61iBool isStarted_Player (const iPlayer *); 61iBool isStarted_Player (const iPlayer *);
62iBool isPaused_Player (const iPlayer *); 62iBool isPaused_Player (const iPlayer *);
63float volume_Player (const iPlayer *); 63float volume_Player (const iPlayer *);
64float time_Player (const iPlayer *); 64float time_Player (const iPlayer *);
65float duration_Player (const iPlayer *); 65float duration_Player (const iPlayer *);
66float streamProgress_Player (const iPlayer *); /* normalized 0...1 */ 66float streamProgress_Player (const iPlayer *); /* normalized 0...1 */
67 67
68uint32_t idleTimeMs_Player (const iPlayer *); 68uint32_t idleTimeMs_Player (const iPlayer *);
69iString * metadataLabel_Player (const iPlayer *); 69iString * 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);
39iBool isPhone_iOS (void); 39iBool isPhone_iOS (void);
40void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom); 40void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom);
41int displayRefreshRate_iOS (void); 41int displayRefreshRate_iOS (void);
42
43/*----------------------------------------------------------------------------------------------*/
44
45iDeclareType(AVFAudioPlayer)
46iDeclareTypeConstruction(AVFAudioPlayer)
47
48iBool setInput_AVFAudioPlayer (iAVFAudioPlayer *, const iString *mediaType, const iBlock *audioFileData);
49void play_AVFAudioPlayer (iAVFAudioPlayer *);
50void stop_AVFAudioPlayer (iAVFAudioPlayer *);
51void setPaused_AVFAudioPlayer (iAVFAudioPlayer *, iBool paused);
52void setVolume_AVFAudioPlayer (iAVFAudioPlayer *, float volume);
53
54double currentTime_AVFAudioPlayer (const iAVFAudioPlayer *);
55double duration_AVFAudioPlayer (const iAVFAudioPlayer *);
56iBool isStarted_AVFAudioPlayer (const iAVFAudioPlayer *);
57iBool 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. */
24#include "app.h" 24#include "app.h"
25#include "ui/command.h" 25#include "ui/command.h"
26#include "ui/window.h" 26#include "ui/window.h"
27
28#include <the_Foundation/file.h>
29#include <the_Foundation/fileinfo.h>
30#include <the_Foundation/path.h>
27#include <SDL_events.h> 31#include <SDL_events.h>
28#include <SDL_syswm.h> 32#include <SDL_syswm.h>
33#include <SDL_timer.h>
29 34
30#import <UIKit/UIKit.h> 35#import <UIKit/UIKit.h>
31#import <CoreHaptics/CoreHaptics.h> 36#import <CoreHaptics/CoreHaptics.h>
37#import <AVFAudio/AVFAudio.h>
32 38
33static iBool isSystemDarkMode_ = iFalse; 39static iBool isSystemDarkMode_ = iFalse;
34static iBool isPhone_ = iFalse; 40static iBool isPhone_ = iFalse;
@@ -223,6 +229,7 @@ void setupApplication_iOS(void) {
223 selector:@selector(keyboardOffScreen:) 229 selector:@selector(keyboardOffScreen:)
224 name:UIKeyboardWillHideNotification 230 name:UIKeyboardWillHideNotification
225 object:nil]; 231 object:nil];
232 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
226} 233}
227 234
228static iBool isDarkMode_(iWindow *window) { 235static iBool isDarkMode_(iWindow *window) {
@@ -309,3 +316,123 @@ void exportDownloadedFile_iOS(const iString *path) {
309 [appState_ setFileBeingSaved:path]; 316 [appState_ setFileBeingSaved:path];
310 [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil]; 317 [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil];
311} 318}
319
320/*----------------------------------------------------------------------------------------------*/
321
322enum iAVFAudioPlayerState {
323 initialized_AVFAudioPlayerState,
324 playing_AVFAudioPlayerState,
325 paused_AVFAudioPlayerState
326};
327
328struct Impl_AVFAudioPlayer {
329 iString cacheFilePath;
330 AVAudioPlayer *player;
331 float volume;
332 enum iAVFAudioPlayerState state;
333};
334
335iDefineTypeConstruction(AVFAudioPlayer)
336
337void init_AVFAudioPlayer(iAVFAudioPlayer *d) {
338 init_String(&d->cacheFilePath);
339 d->player = NULL;
340 d->volume = 1.0f;
341 d->state = initialized_AVFAudioPlayerState;
342}
343
344void deinit_AVFAudioPlayer(iAVFAudioPlayer *d) {
345 setInput_AVFAudioPlayer(d, NULL, NULL);
346}
347
348static const char *cacheDir_ = "~/Library/Caches/Audio";
349
350static const char *fileExt_(const iString *mimeType) {
351 /* Media types that AVFAudioPlayer will try to play. */
352 if (startsWithCase_String(mimeType, "audio/aiff") ||
353 startsWithCase_String(mimeType, "audio/x-aiff")) {
354 return ".aiff";
355 }
356 if (startsWithCase_String(mimeType, "audio/3gpp")) return ".3gpp";
357 if (startsWithCase_String(mimeType, "audio/mpeg")) return ".mp3";
358 if (startsWithCase_String(mimeType, "audio/mp3")) return ".mp3";
359 if (startsWithCase_String(mimeType, "audio/mp4")) return ".mp4";
360 if (startsWithCase_String(mimeType, "audio/mpeg4")) return ".mp4";
361 if (startsWithCase_String(mimeType, "audio/aac")) return ".aac";
362 return "";
363}
364
365iBool setInput_AVFAudioPlayer(iAVFAudioPlayer *d, const iString *mimeType, const iBlock *audioFileData) {
366 if (!isEmpty_String(&d->cacheFilePath)) {
367 remove(cstr_String(&d->cacheFilePath));
368 clear_String(&d->cacheFilePath);
369 }
370 if (d->player) {
371 d->player = nil;
372 }
373 if (mimeType && audioFileData && iCmpStr(fileExt_(mimeType), "")) {
374 makeDirs_Path(collectNewCStr_String(cacheDir_));
375 iFile *f = new_File(collectNewFormat_String("%s/%u%s", cacheDir_, SDL_GetTicks(), fileExt_(mimeType)));
376 if (open_File(f, writeOnly_FileMode)) {
377 write_File(f, audioFileData);
378 set_String(&d->cacheFilePath, path_File(f));
379 NSError *error = nil;
380 d->player = [[AVAudioPlayer alloc]
381 initWithContentsOfURL:[NSURL fileURLWithPath:
382 [NSString stringWithUTF8String:cstr_String(&d->cacheFilePath)]]
383 error:&error];
384 if (error) {
385 d->player = nil;
386 }
387 [d->player setVolume:d->volume];
388 }
389 iRelease(f);
390 }
391 return d->player != nil;
392}
393
394void play_AVFAudioPlayer(iAVFAudioPlayer *d) {
395 if (d->state != playing_AVFAudioPlayerState) {
396 [d->player play];
397 d->state = playing_AVFAudioPlayerState;
398 }
399}
400
401void stop_AVFAudioPlayer(iAVFAudioPlayer *d) {
402 [d->player stop];
403 d->state = initialized_AVFAudioPlayerState;
404}
405
406void setPaused_AVFAudioPlayer(iAVFAudioPlayer *d, iBool paused) {
407 if (paused && d->state != paused_AVFAudioPlayerState) {
408 [d->player pause];
409 d->state = paused_AVFAudioPlayerState;
410 }
411 else if (!paused && d->state != playing_AVFAudioPlayerState) {
412 [d->player play];
413 d->state = playing_AVFAudioPlayerState;
414 }
415}
416
417void setVolume_AVFAudioPlayer(iAVFAudioPlayer *d, float volume) {
418 d->volume = volume;
419 if (d->player) {
420 [d->player setVolume:volume];
421 }
422}
423
424double currentTime_AVFAudioPlayer(const iAVFAudioPlayer *d) {
425 return [d->player currentTime];
426}
427
428double duration_AVFAudioPlayer(const iAVFAudioPlayer *d) {
429 return [d->player duration];
430}
431
432iBool isStarted_AVFAudioPlayer(const iAVFAudioPlayer *d) {
433 return d->state != initialized_AVFAudioPlayerState;
434}
435
436iBool isPaused_AVFAudioPlayer(const iAVFAudioPlayer *d) {
437 return d->state == paused_AVFAudioPlayerState;
438}
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
312 audio = at_PtrArray(&d->audio, existing - 1); 312 audio = at_PtrArray(&d->audio, existing - 1);
313 iAssert(equal_String(&audio->props.mime, mime)); /* MIME cannot change */ 313 iAssert(equal_String(&audio->props.mime, mime)); /* MIME cannot change */
314 updateSourceData_Player(audio->player, mime, data, append_PlayerUpdate); 314 updateSourceData_Player(audio->player, mime, data, append_PlayerUpdate);
315 if (!isPartial) {
316 updateSourceData_Player(audio->player, NULL, NULL, complete_PlayerUpdate);
317 }
315 if (!isStarted_Player(audio->player)) { 318 if (!isStarted_Player(audio->player)) {
316 /* Maybe the previous updates didn't have enough data. */ 319 /* Maybe the previous updates didn't have enough data. */
317 start_Player(audio->player); 320 start_Player(audio->player);
318 } 321 }
319 if (!isPartial) {
320 updateSourceData_Player(audio->player, NULL, NULL, complete_PlayerUpdate);
321 }
322 } 322 }
323 } 323 }
324 else if ((existing = findLinkDownload_Media(d, linkId)) != 0) { 324 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) {
710 if (document_App() != d) { 710 if (document_App() != d) {
711 return 0; 711 return 0;
712 } 712 }
713 if (get_Window()->isDrawFrozen) {
714 return 0;
715 }
713 static const uint32_t invalidInterval_ = ~0u; 716 static const uint32_t invalidInterval_ = ~0u;
714 uint32_t interval = invalidInterval_; 717 uint32_t interval = invalidInterval_;
715 iConstForEach(PtrArray, i, &d->visibleMedia) { 718 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) {
1887 SDL_ShowWindow(d->win); 1887 SDL_ShowWindow(d->win);
1888 } 1888 }
1889 postRefresh_App(); 1889 postRefresh_App();
1890 postCommand_App("media.player.update"); /* in case a player needs updating */
1890 return iTrue; 1891 return iTrue;
1891 } 1892 }
1892 if (processEvent_Touch(&event)) { 1893 if (processEvent_Touch(&event)) {