[Xamarin Blog] Background Audio Streaming with Xamarin.Android

JamesMontemagnoJamesMontemagno USForum Administrator, Xamarin Team, Developer Group Leader Xamurai
edited January 2014 in Xamarin.Android

Just posted: Background Audio Streaming with Xamarin.Android on the Xamarin Blog.

Read the full article to learn how to up stream music in a background service in Xamarin.Android and take advantage of notifications.

Use this thread to discuss or ask questions.

«1

Posts

  • DannyDanny USMember

    Doesn't this cause hiccups on android devices when the GC runs? I've run into this issue many times with larger programs and have had to go out of process to solve this.

  • crocacroca ARMember

    Hello James, I'm a beginner with Xamarin Android, i'm trying to play MP3 based on PendingIntent triggered when using AddProximityAlert.
    I was trying to use part of your sample, the problem is, your code implement Android.Content.Content Intent in the MainActivity SendAudioCommand, but I need to use an Android.App:PendingIntent Intent. I cannot compile.
    Is there any suggestion/idea you can provide?.
    I will try to figure it out in the meantime.
    Thanks in advance

  • JamesMontemagnoJamesMontemagno USForum Administrator, Xamarin Team, Developer Group Leader Xamurai
    edited February 2014

    @Danny, I have not seen this be an issue at all since the audio is streaming as a foreground service. This is the design pattern that Google put in place, so it shouldn't be an issue.

    @croca, I am not positive on that one, do you have a small code example that you could share?

  • MichalMandryszMichalMandrysz USMember

    Hi @JamesMontemagno‌,
    Is there a way to make it stream m3u files?

    Thanks,

  • JamesMontemagnoJamesMontemagno USForum Administrator, Xamarin Team, Developer Group Leader Xamurai
    edited March 2014

    @MichalMandrysz‌ out of the box Android does not support m3u files. http://developer.android.com/guide/appendix/media-formats.html However I have seen other apps do this, but they are probably parsing the m3u themselves.

  • MichalMandryszMichalMandrysz USMember

    @JamesMontemagno‌ that's what I thought... Thanks!

  • litofransilvalitofransilva PHMember

    are there ways to stream audio from a tv broadcast, like those found in gyms

  • ChristopherDrososChristopherDrosos GRMember ✭✭

    In order to change this code to work with audio from the app i have to change this code:

    player.SetAudioStreamType(Stream.Music);
    await player.SetDataSourceAsync(ApplicationContext, Android.Net.Uri.Parse(Mp3));

    to something like this:

    player= MediaPlayer.Create (this, Resource.Raw.test);

    and to remove the wifilock?

  • JamesMontemagnoJamesMontemagno USForum Administrator, Xamarin Team, Developer Group Leader Xamurai

    Looks like you are plating it from a file.

    You wouldn't need the wifilock at all. Take a look at: https://github.com/chrisntr/DaysUntilXmas/blob/master/Android/DaysUntilXmasAndroid/MainActivity.cs#L118

  • ChristopherDrososChristopherDrosos GRMember ✭✭
    edited July 2014

    I see this code has player.Completion event,
    great
    because i want my music file to play from the beggining and loop only a part of the file (ex loop part 00:02 - end of file)
    i guess with this code i can loop it without noticing any brakes on the audio?

    EDIT: Also something else, in my app i use 3 mediaplayers at the same time because i want to be able to start the track of the other players with a fadeout effect. I am using pause when the fadeout is completed instead of stop and release. is this ok for a program to have 3 music tracks prepared or is it too heavy? if i want to replace 1 of the 3 tracks i am releasing it first and then changing it

  • ChristopherDrososChristopherDrosos GRMember ✭✭
    edited August 2014

    Im getting the name startservice does not exist in the current context;

    var intent = new Intent(PlayingBackgroundService.ActionPlay);
                    StartService(intent);
    

    im trying to do this from a fragment although. this can only be done from activities?

    if i put the above code on a function on MainActivity and do something like this ((MainActivity)Activity).PlayFunction(); is it ok?

    I also get this exception:

    Neither user 10052 nor current process has android.permission.WAKE_LOCK.

    from this line:

    player.SetWakeMode(ApplicationContext, WakeLockFlags.Partial);

  • JamesMontemagnoJamesMontemagno USForum Administrator, Xamarin Team, Developer Group Leader Xamurai

    A fragment can not start an activity or start a service, only an Activity can.

    You must use reference to your parent activity with:

    Activity.StartService(intent);

    For the wake lock did you set the wake lock permission?
    https://github.com/jamesmontemagno/AndroidStreamingAudio/blob/master/Part 1 - Simple Streaming/Properties/AssemblyInfo.cs#L35

  • ChristopherDrososChristopherDrosos GRMember ✭✭
    edited August 2014

    thanks very much, working great

    im setting intent on Main activity like this var intent = new Intent(PlayingBackgroundService.ActionPlay); but from the fragment the code Activity.StartService(intent); or MainActivity.StartService(intent); doesnt work im using ((MainActivity)Activity).PlayFunction(); for now

    Also this line: player.SetAudioStreamType(Stream.Music); in needed in my occasion?

  • ChristopherDrososChristopherDrosos GRMember ✭✭
    edited August 2014

    If we want to pass variables into the service, like the music file resource id we want to play, how we can do it? should we pass variables to the service or maybe a better approach is to have a public class with public variables and use those? (like in this App https://github.com/chrisntr/DaysUntilXmas/blob/master/Android/DaysUntilXmasAndroid/Helpers/Settings.cs )

  • JamesMontemagnoJamesMontemagno USForum Administrator, Xamarin Team, Developer Group Leader Xamurai

    If you are going to stream in the background you could just pass the ID through the Intent.

  • ChristopherDrososChristopherDrosos GRMember ✭✭

    i don't know how to do that, sorry.
    i have try to do something like this [IntentFilter(new[] { ActionPlay(int ID) but it's not working

  • JamesMontemagnoJamesMontemagno USForum Administrator, Xamarin Team, Developer Group Leader Xamurai
    edited August 2014

    When you pass it down:

    private void SendAudioCommand(string action, int Id)
    {
      var intent = new Intent(action);
      intent.PutExtra("id", id)
      StartService(intent);
    }
    

    Get it out:

    public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
     {
    
      var id = intent.GetIntExtra("id", -1);
      if(id != -1)
        //do something
    
      switch (intent.Action) {
        case ActionPlay: Play(); break;
        case ActionStop: Stop(); break;
        case ActionPause: Pause(); break;
      }
    
      //Set sticky as we are a long running operation
      return StartCommandResult.Sticky;
    }
    
  • ChristopherDrososChristopherDrosos GRMember ✭✭

    ah great it's that simple, like passing data into other activity, i haven't figure it out until now, thanks

  • ChristopherDrososChristopherDrosos GRMember ✭✭

    I have a function that fades in and out a mediaplayer. so far when i want to change the track on a mediaplayer i have to fadeout a mediaplayer and to start the track and the fade in on some other mediaplayer like this

            public async void FadeOut(SeekBar seekBar,MediaPlayer player, int sec)
            {
                await Task.Run (() => {
                    float iter = (float)1/sec;
                    for (int i = 1; i <= sec; i++) {
                        Task.Delay(1000);
                        float vol = (float)1-(iter*i);
                        player.SetVolume ( vol, vol);
                    }
                    player.SetVolume(0,0);
                });
    

    But this requires to have 2 media players defined. Do you have any idea how i can do that without having to cerate 2 mediaplayers? I want to use this on the backround audio streaming service. so far i always have defined 2 mediaplayers and they swich every time i want to change a track (the one that isPlaying fade's out and the other starting playing with fade in) but this doesn't sound like a good way of playing music

  • If We want to also have Playlist with our mediaPlayer how can we handle the Service<->Fragment communication? I think we only need to notify the fragment that the track has been changed and then the fragment can take the list position or title etc from SharedPreferences or something

  • AliAkramAliAkram USMember
    edited January 2015

    can some one give me idea how to customise the notification view? means now it just says "Xamarin Steaming" as title and "playing music" as text. I am want the large Icon of artist on left side and also the play,pause,next,previous buttons in same notification view. Question is is there some default way to do that or should I have to make custom view? Any help will be a lot for me. Up till now I was only able to make an icon on left side. Need to know how to add other controls in same view.
    This is what I want i.stack.imgur.com/HXurE.png

  • JamesMontemagnoJamesMontemagno USForum Administrator, Xamarin Team, Developer Group Leader Xamurai

    @AliAkram, you can pass it any notification you want. You just need to construct what you want to show. Maybe the Big View would work for you, else you will need to go with something such as a custom view.

    There are some good xamarin docs on getting started, but dive into the android docs as well:

    http://developer.xamarin.com/guides/cross-platform/application_fundamentals/notifications/android/local_notifications_in_android/

    http://developer.xamarin.com/guides/android/platform_features/introduction_to_jelly_bean/

    http://developer.android.com/training/notify-user/expanded.html

  • AliAkramAliAkram USMember

    @JamesMontemagno‌ Thanks a lot for providing these links. I made what I was needed. Now a little question how to add listeners behind the action buttons we add in builder?
    Below is attached to what I created.

  • CasperSkouboCasperSkoubo USUniversity, Developer Group Leader

    You should add PendingIntents with your actions. Then have a broadcast receiver listening for the intents sent. Try and post your code, it might also be useful to other people.

  • ebetzlerebetzler USMember ✭✭

    James- I am trying to implement Android background streaming audio in a shared Xamarin Forms 1.3 project.
    I am using a bound service (bound on-demand) and starting in the foreground as you have done.
    If I minimize the app (via the HOME button) and then drop down the notifications and click on the service item, my Xamarin.Forms.Application ends up in the OnStart state instead of the OnResume state. However if I minimize the app, then click the app icon to re-open, I get expected behavior (OnResume is called). Am I misunderstanding what should happen? or is something wrong here?

    // inside my service class ...
        private void StartForeground()
        {
        var pendingIntent = PendingIntent.GetActivity(
                Application.Context, 
                0,
                new Intent(Application.Context, typeof(MyApp.MainActivity)),
                PendingIntentFlags.UpdateCurrent);
    
        var bmp = BitmapFactory.DecodeResource(Resources, Resource.Mipmap.ic_launcher);
    
        var n = new Notification.Builder (ApplicationContext)
                .SetContentTitle ("My App")
                .SetContentText ("Playing audio!")
                .SetTicker ("Audio started!")
                .SetSmallIcon (Resource.Drawable.service_icon)
                .SetLargeIcon (bmp)
                .SetOngoing (true)
                .SetContentIntent (pendingIntent);
    
        base.StartForeground(NotificationId, n.Build());
        }
    
    // inside my activity class ...
        [Activity (Label = "My App", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
        public class MainActivity : FormsApplicationActivity
        {
                protected override void OnCreate (Bundle bundle)
                {
                    base.OnCreate (bundle);
    
                    Xamarin.Forms.Forms.Init (this, bundle);
    
                    LoadApplication (new MyApp.App());
                }
        }
    
  • JamesMontemagnoJamesMontemagno USForum Administrator, Xamarin Team, Developer Group Leader Xamurai

    You should make your [Activity ( properties with:

    LaunchMode = LaunchMode.SingleTask,

    If you don't then when your user taps to launch the mainactivity it will start a new one.

  • ebetzlerebetzler USMember ✭✭

    Thank you. Thank you. Thank you!
    Hunted around for hours trying to figure that out, with no success.

  • facussfacuss ARMember

    hi, i use your code for my app for android streaming radio, and it works great . except that sometimes when I open , it closes . If I delete the data in the app working again. I was thinking something like, for example, when the app is closed the data is deleted. Is there any solution? thx james ...

  • ChristopherDrososChristopherDrosos GRMember ✭✭

    facuss your problem is probably more related to something else rather than this thread, please make a new thread with your code and i will try to help you

  • facussfacuss ARMember

    Christopher thanks for replying, that part of the code have to publish?

  • ChristopherDrososChristopherDrosos GRMember ✭✭

    tha part that you store your data probably

  • pilatespilates TRMember ✭✭
    edited June 2015

    I am having trouble with handling audio focus.

    I have this line inside my Play() function

    var focusResult = audioManager.RequestAudioFocus(this, Stream.Music, AudioFocus.Gain);

    When this line is called I am getting the following error in the console

    Unable to start playback: System.NullReferenceException: Object reference not set to an instance of an object
    at Radio.Droid.Services.StreamingBackgroundService+c__async0.MoveNext () [0x00000] in :0

    When I comment out the line I don't get any errors.

    And I have the listener as follows. But I don't see the connection between the above line and the listener below. Shouldn't the above code be

    var focusResult = audioManager.RequestAudioFocus(OnAudioFocusChange, Stream.Music, AudioFocus.Gain);

    I tried this but I am getting the following errors

    Error CS1502: The best overloaded method match for `Android.Media.AudioManager.RequestAudioFocus(Android.Media.AudioManager.IOnAudioFocusChangeListener, Android.Media.Stream, Android.Media.AudioFocus)' has some invalid arguments (CS1502)

    Error CS1503: Argument #1' cannot convert method group' expression to type `Android.Media.AudioManager.IOnAudioFocusChangeListener' (CS1503)

    Finally, does this also handle incoming phone calls?

    public void OnAudioFocusChange(AudioFocus focusChange)
            {
                switch (focusChange)
                {
                case AudioFocus.Gain:
                    if (player == null)
                        IntializePlayer();
    
                    if (!player.IsPlaying)
                    {
                        player.Start();
                        paused = false;
                    }
    
                    player.SetVolume(1.0f, 1.0f);//Turn it up!
                    break;
                case AudioFocus.Loss:
                    //We have lost focus stop!
                    Stop();
                    break;
                case AudioFocus.LossTransient:
                    //We have lost focus for a short time, but likely to resume so pause
                    Pause();
                    break;
                case AudioFocus.LossTransientCanDuck:
                    //We have lost focus but should till play at a muted 10% volume
                    if(player.IsPlaying)
                        player.SetVolume(.1f, .1f);//turn it down!
                    break;
    
                }
            }
    
  • Martijn00Martijn00 NLInsider, University ✭✭✭

    @MuratKaya.3905 I don't think there is anything wrong with RequestAudioFocus(this, ...) the this references the AudioManager.IOnAudioFocusChangeListener which is implemented in the service. The error rather has something to do with the method itself. Try putting in a breakpoint somewhere there and see where the error happens.

    About phone calls: this those something with phone calls, but might not be the best solution. See this thread: http://stackoverflow.com/questions/5610464/stopping-starting-music-on-incoming-calls

  • Martijn00Martijn00 NLInsider, University ✭✭✭

    I've made an example which is available here

  • MattHarringtonMattHarrington USMember, University

    This sequence appears to crash the sample:

    1. Deploy a Release build to a real device. I can't reproduce this with a Debug build. I deployed to a Galaxy Nexus running 4.3.
    2. Play music with the headphones connected
    3. Quit the app using the task switcher. Background audio continues since it's in a service.
    4. Unplug the headphones

    The app then crashes with a NullReferenceException as seen in logcat below. It's not obvious to me what in OnStartCommand() is null, as the exception seems to be thrown before any Console.WriteLine() statements are reached. Any ideas, @JamesMontemagno?

    ... Music is playing.  Unplug headphones here ...
    
    
    I/EventHub(  890): Removing device sec_jack due to epoll hang-up event.
    I/EventHub(  890): Removed device: path=/dev/input/event6 name=sec_jack id=23 fd=204 classes=0x1
    I/InputReader(  890): Device removed: id=23, name='sec_jack', sources=0x00000101
    I/EventHub(  890): Removing device '/dev/input/event6' due to inotify event
    V/WiredAccessoryManager(  890): Headset UEVENT: {SUBSYSTEM=switch, SWITCH_STATE=0, DEVPATH=/devices/virtual/switch/h2w, SEQNUM=58360, ACTION=change, SWITCH_NAME=h2w, SWITCH_TIME=9004954696574}
    V/WiredAccessoryManager(  890): newName=h2w newState=0 headsetState=0 prev headsetState=1
    I/ActivityManager(  890): Config changes=1400 {1.0 999mcc?mnc en_US ldltr sw360dp w360dp h567dp 320dpi nrml port finger -keyb/v/h -nav/h s.49}
    V/WiredAccessoryManager(  890): device h2w disconnected
    D/PhoneStatusBar( 1005): mSettingsPanelGravity = 55
    I/ActivityManager(  890): Killing 18597:BackgroundStreamingAudio.BackgroundStreamingAudio/u0a10070: remove task
    W/TimedEventQueue(  655): Event 23 was not found in the queue, already cancelled?
    W/AudioFlinger(  655): session id 209 not found for pid 655
    W/AudioService(  890):   AudioFocus   audio focus client died
    I/AudioService(  890):  AudioFocus  abandonAudioFocus(): removing entry for [email protected][email protected]
    W/ActivityManager(  890): Scheduling restart of crashed service BackgroundStreamingAudio.BackgroundStreamingAudio/md5a9f4a8a1e468a690e19fdcd49d20000d.StreamingBackgroundService in 5000ms
    
    
    ... Headphones are unplugged.  Audio has stopped ...
    
    
    D/dalvikvm( 1005): GC_CONCURRENT freed 274K, 35% free 13415K/20636K, paused 4ms+6ms, total 40ms
    I/ActivityManager(  890): Start proc BackgroundStreamingAudio.BackgroundStreamingAudio for service BackgroundStreamingAudio.BackgroundStreamingAudio/md5a9f4a8a1e468a690e19fdcd49d20000d.StreamingBackgroundService: pid=18696 uid=10070 gids={50070, 3003, 1015, 1028}
    D/dalvikvm(18696): Trying to load lib /data/app-lib/BackgroundStreamingAudio.BackgroundStreamingAudio-1/libmonodroid.so 0x42802c70
    D/dalvikvm(18696): Added shared lib /data/app-lib/BackgroundStreamingAudio.BackgroundStreamingAudio-1/libmonodroid.so 0x42802c70
    W/monodroid(18696): Trying to load sgen from: /data/app-lib/BackgroundStreamingAudio.BackgroundStreamingAudio-1/libmonosgen-2.0.so
    W/monodroid-gc(18696): GREF GC Threshold: 46080
    I/MonoDroid(18696): UNHANDLED EXCEPTION:
    I/MonoDroid(18696): System.NullReferenceException: Object reference not set to an instance of an object
    I/MonoDroid(18696): at BackgroundStreamingAudio.Services.StreamingBackgroundService.OnStartCommand (Android.Content.Intent,Android.App.StartCommandFlags,int) <0x0001c>
    I/MonoDroid(18696): at Android.App.Service.n_OnStartCommand_Landroid_content_Intent_II (intptr,intptr,intptr,int,int) <0x00073>
    I/MonoDroid(18696): at (wrapper dynamic-method) object.deb5b602-b7fc-4009-85ee-0be767535beb (intptr,intptr,intptr,int,int) <0x00063>
    D/AndroidRuntime(18696): Shutting down VM
    W/dalvikvm(18696): threadid=1: thread exiting with uncaught exception (group=0x41f52700)
    E/AndroidRuntime(18696): FATAL EXCEPTION: main
    E/AndroidRuntime(18696): java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    E/AndroidRuntime(18696):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
    E/AndroidRuntime(18696):    at dalvik.system.NativeStart.main(Native Method)
    E/AndroidRuntime(18696): Caused by: java.lang.reflect.InvocationTargetException
    E/AndroidRuntime(18696):    at java.lang.reflect.Method.invokeNative(Native Method)
    E/AndroidRuntime(18696):    at java.lang.reflect.Method.invoke(Method.java:525)
    E/AndroidRuntime(18696):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
    E/AndroidRuntime(18696):    ... 2 more
    E/AndroidRuntime(18696): Caused by: md52ce486a14f4bcd95899665e9d932190b.JavaProxyThrowable: System.NullReferenceException: Object reference not set to an instance of an object
    E/AndroidRuntime(18696): at BackgroundStreamingAudio.Services.StreamingBackgroundService.OnStartCommand (Android.Content.Intent,Android.App.StartCommandFlags,int) <0x0001c>
    E/AndroidRuntime(18696): at Android.App.Service.n_OnStartCommand_Landroid_content_Intent_II (intptr,intptr,intptr,int,int) <0x00073>
    E/AndroidRuntime(18696): at (wrapper dynamic-method) object.deb5b602-b7fc-4009-85ee-0be767535beb (intptr,intptr,intptr,int,int) <0x00063>
    E/AndroidRuntime(18696): 
    E/AndroidRuntime(18696):    at md5a9f4a8a1e468a690e19fdcd49d20000d.StreamingBackgroundService.n_onStartCommand(Native Method)
    E/AndroidRuntime(18696):    at md5a9f4a8a1e468a690e19fdcd49d20000d.StreamingBackgroundService.onStartCommand(StreamingBackgroundService.java:49)
    E/AndroidRuntime(18696):    at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2704)
    E/AndroidRuntime(18696):    at android.app.ActivityThread.access$1900(ActivityThread.java:141)
    E/AndroidRuntime(18696):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1353)
    E/AndroidRuntime(18696):    at android.os.Handler.dispatchMessage(Handler.java:99)
    E/AndroidRuntime(18696):    at android.os.Looper.loop(Looper.java:137)
    E/AndroidRuntime(18696):    at android.app.ActivityThread.main(ActivityThread.java:5103)
    E/AndroidRuntime(18696):    ... 5 more
    D/dalvikvm( 1089): GC_CONCURRENT freed 354K, 6% free 9385K/9944K, paused 4ms+3ms, total 31ms
    W/ActivityManager(  890): Timeout executing service: ServiceRecord{430b8c70 u0 BackgroundStreamingAudio.BackgroundStreamingAudio/md5a9f4a8a1e468a690e19fdcd49d20000d.StreamingBackgroundService}
    I/ActivityManager(  890): Crashing app skipping ANR: ProcessRecord{42e50860 18696:BackgroundStreamingAudio.BackgroundStreamingAudio/u0a10070} Executing service BackgroundStreamingAudio.BackgroundStreamingAudio/md5a9f4a8a1e468a690e19fdcd49d20000d.StreamingBackgroundService
    
  • JamesMontemagnoJamesMontemagno USForum Administrator, Xamarin Team, Developer Group Leader Xamurai

    Inside of your OnStartCommand something is null, Simply do some null checking in there and logging to investigate deeper.

  • MattHarringtonMattHarrington USMember, University

    Inside of your OnStartCommand something is null, Simply do some null checking in there and logging to investigate deeper.

    I must not have emphasized that I know something in OnStartCommand() is null, and that a statement checking for null was not reached, even when it was the first statement in OnStartCommand(). However, restarting Xamarin Studio seems to have fixed a problem and I was able to track it down. I'm still unused to restarting my IDE when something isn't working as expected.

    I'm just working with the sample from the blog post, without modifications.

    The problem was that the Intent was null. I overlooked part of the Xamarin documentation which states that sticky services are restarted with a null Intent. When using sticky services, that case must be handled.

    I ended up switching to a 3rd party library called aacdecoder-android for my project, so I don't have modifications to the sample I can submit as a pull request, but here are some thoughts for others who might try to incorporate the sample into their own project:

    • Sticky services are restarted by Android, so an app must handle the lifecycle of any service it creates. In particular, consider calling Service.StopSelf() when audio has stopped playing.
    • Sticky services are often recommended for background audio, but consider using StartCommandResult.NotSticky or StartCommandResult.RedeliverIntent instead. These options seem to make handling the service's lifecycle easier.
    • It's best to not play audio unless audio focus was granted
    • When stopping audio, abandon audio focus
    • I prefer to register broadcast receivers dynamically using Context.RegisterReceiver() and Context.UnregisterReveiver(). When handling ActionAudioBecomingNoisy, register the receiver when playing and unregister it when audio has stopped. In the sample, the receiver gets statically published in the manifest, so it will always run and respond even if another app is playing audio.

    I think the sample is a port from code straight from the Android documentation, but the above considerations worked for me.

    Matt

  • ebetzlerebetzler USMember ✭✭

    @JamesMontemagno , how do you change the playback rate of Android's mediaplayer?

  • koly862koly862 RUMember
    edited March 2017

    how can I to solve the problem

Sign In or Register to comment.