Android Open Source - intent_radio Intent Player






From Project

Back to project page intent_radio.

License

The source code is released under:

Copyright (c) 2014 Stephen Blott Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Soft...

If you think the Android project intent_radio listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package org.smblott.intentradio;
//from   w  w w. j a  va 2  s . c om
import android.app.Service;
import android.content.Intent;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.AsyncTask;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.StrictMode;

import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;

import android.media.MediaPlayer;
import android.media.MediaPlayer.OnBufferingUpdateListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnInfoListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.media.MediaPlayer.OnCompletionListener;

import android.net.Uri;
import android.os.Build.VERSION;
import android.webkit.URLUtil;

public class IntentPlayer extends Service
   implements
      OnBufferingUpdateListener,
      OnInfoListener,
      OnErrorListener,
      OnPreparedListener,
      OnAudioFocusChangeListener,
      OnCompletionListener
{

   /* ********************************************************************
    * Globals...
    */

   private static final int note_id = 100;
   private static final String preference_file = "state";
   private static SharedPreferences settings = null;

   private static Context context = null;

   private static String app_name = null;
   private static String app_name_long = null;
   private static String intent_play = null;
   private static String intent_stop = null;
   private static String intent_pause = null;
   private static String intent_restart = null;
   private static String intent_state_request = null;
   private static String intent_click = null;

   private static String default_url = null;
   private static String default_name = null;
   public  static String name = null;
   public  static String url = null;

   private static MediaPlayer player = null;
   private static AudioManager audio_manager = null;

   private static Playlist playlist_task = null;
   private static AsyncTask<Integer,Void,Void> pause_task = null;

   private static Connectivity connectivity = null;
   private static int initial_failure_ttl = 5;
   private static int failure_ttl = 0;

   /* ********************************************************************
    * Create service...
    */

   @Override
   public void onCreate() {
      context = getApplicationContext();
      Logger.init(context);
      Notify.init(this,context);

      app_name = getString(R.string.app_name);
      app_name_long = getString(R.string.app_name_long);
      intent_play = getString(R.string.intent_play);
      intent_stop = getString(R.string.intent_stop);
      intent_pause = getString(R.string.intent_pause);
      intent_restart = getString(R.string.intent_restart);
      intent_state_request = context.getString(R.string.intent_state_request);
      intent_click = getString(R.string.intent_click);
      default_url = getString(R.string.default_url);
      default_name = getString(R.string.default_name);

      settings = getSharedPreferences(preference_file, context.MODE_PRIVATE);
      url = settings.getString("url", default_url);
      name = settings.getString("name", default_name);

      audio_manager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
      connectivity = new Connectivity(context,this);
   }

   /* ********************************************************************
    * Destroy service...
    */

   public void onDestroy()
   {
      log("Destroyed.");
      stop();

      if ( player != null )
      {
         player.release();
         player = null;
      }

      if ( connectivity != null )
      {
         connectivity.destroy();
         connectivity = null;
      }

      Logger.state("off");
      super.onDestroy();
   }

   /* ********************************************************************
    * Main entry point...
    */

   @Override
   public int onStartCommand(Intent intent, int flags, int startId)
   {
      if ( intent == null || ! intent.hasExtra("action") )
         return done();

      if ( intent.hasExtra("debug") )
         Logger.state(intent.getStringExtra("debug"));

      if ( ! Counter.still(intent.getIntExtra("counter", Counter.now())) )
         return done();

      String action = intent.getStringExtra("action");
      log("Action: ", action);

      if ( action.equals(intent_stop)    ) return stop();
      if ( action.equals(intent_pause)   ) return pause();
      if ( action.equals(intent_restart) ) return restart();
      if ( action.equals(intent_click)   ) return click();

      if ( action.equals(intent_state_request) )
      {
         State.get_state(context);
         return done();
      }

      if ( action.equals(intent_play) )
      {
         if ( intent.hasExtra("url") )
            url = intent.getStringExtra("url");

         if ( intent.hasExtra("name") )
            name = intent.getStringExtra("name");

         Editor editor = settings.edit();
         editor.putString("url", url);
         editor.putString("name", name);
         editor.commit();

         log("Name: ", name);
         log("URL: ", url);
         Notify.name(name);
         failure_ttl = initial_failure_ttl;
         return play(url);
      }

      log("unknown action: ", action);
      return done();
   }

   /* ********************************************************************
    * Play...
    */

   public int play()
      { return play(url); }

   private int play(String url)
   {
      stop(false);

      toast(name);
      log("Play: ", url);

      if ( ! URLUtil.isValidUrl(url) )
      {
         toast("Invalid URL.");
         return stop();
      }

      if ( isNetworkUrl(url) && ! connectivity.isConnected(context) )
      {
         toast("No internet connection.");
         // We'll pretend that we dropped the connection.  That way, when we
         // get a connection, playback will start.
         connectivity.dropped_connection();
         return done();
      }

      int focus = audio_manager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
      if ( focus != AudioManager.AUDIOFOCUS_REQUEST_GRANTED )
      {
         toast("Could not obtain audio focus.");
         return stop();
      }

      // /////////////////////////////////////////////////////////////////
      // Set up media player...

      if ( player == null )
      {
         log("Creating media player...");
         player = new MediaPlayer();
         player.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
         player.setAudioStreamType(AudioManager.STREAM_MUSIC);
         player.setOnPreparedListener(this);
         player.setOnBufferingUpdateListener(this);
         player.setOnInfoListener(this);
         player.setOnErrorListener(this);
         player.setOnCompletionListener(this);
      }

      if ( isNetworkUrl(url) )
         WifiLocker.lock(context, app_name_long);

      log("Connecting...");
      playlist_task = new Playlist(this,url).start();

      // The Playlist object calls play_launch(url), when it's ready.
      start_buffering();
      return done(State.STATE_BUFFER);
   }

   /* ********************************************************************
    * Launch player...
    */

   // The launch_url may be different from the original URL.  For example, it
   // could be the URL extracted from a playlist, whereas the original url is
   // that of the playlist itself.
   private static String launch_url = null;

   public int play_launch(String url)
   {
      log("Launching: ", url);

      launch_url = null;
      if ( ! URLUtil.isValidUrl(url) )
      {
         toast("Invalid URL.");
         return stop();
      }

      launch_url = url;

      // Note:  Because of the way we handle network connectivity, the player
      // always stops and then restarts as we move between network types.
      // Therefore, stop() and start() are always called.  So we always have
      // the WiFi lock if we're on WiFi and we need it, and don't otherwise.
      // 
      // Here, we could be holding a WiFi lock because the playlist URL was a
      // network URL, but perhaps now the launch URL is not.  Or the other way
      // around.  So release the WiFi lock (if it's being held) and reaquire
      // it, if necessary.
      WifiLocker.unlock();
      if ( isNetworkUrl(url) )
         WifiLocker.lock(context, app_name_long);

      try
      {
         player.setVolume(1.0f, 1.0f);
         player.setDataSource(context, Uri.parse(url));
         player.prepareAsync();
      }
      catch (Exception e)
         { return stop(); }

      start_buffering();
      return done(State.STATE_BUFFER);
   }

   @Override
   public void onPrepared(MediaPlayer mp)
   {
      if ( mp == player )
      {
         log("Starting....");
         player.start();
         // Invalidate any outstanding stop_soon threads, or the like.
         Counter.time_passes();
         // Allow future restarts after failure.
         failure_ttl = initial_failure_ttl;
         State.set_state(context, State.STATE_PLAY, isNetworkUrl());
      }
   }

   public boolean isNetworkUrl()
      { return isNetworkUrl(launch_url); }

   public boolean isNetworkUrl(String check_url)
      { return ( check_url != null && URLUtil.isNetworkUrl(check_url) ); }

   /* ********************************************************************
    * Stop...
    */

   public int stop()
      { return stop(true); }

   private int stop(boolean update_state)
   {
      log("Stopping");

      Counter.time_passes();
      launch_url = null;
      audio_manager.abandonAudioFocus(this);
      WifiLocker.unlock();

      if ( player != null )
      {
         log("Stopping/releasing player...");
         if ( player.isPlaying() )
            player.stop();
         player.reset();
         player.release();
         player = null;
      }

      if ( playlist_task != null )
      {
         playlist_task.cancel(true);
         playlist_task = null;
      }

      if ( update_state )
         return done(State.STATE_STOP);
      else
         return done();
   }

   /* ********************************************************************
    * Reduce volume, for a short while, for a notification.
    */

   private int duck(String msg)
   {
      log("Duck: ", State.current());

      if ( State.is(State.STATE_DUCK) || ! State.is_playing() )
         return done();

      player.setVolume(0.1f, 0.1f);
      return done(State.STATE_DUCK);
   }

   /* ********************************************************************
    * Pause/restart...
    */

   private int pause()
   {
      log("Pause: ", State.current());

      if ( player == null || State.is(State.STATE_PAUSE) || ! State.is_playing() )
         return done();

      if ( pause_task != null )
         pause_task.cancel(true);

      // We're still holding resources, including a possibly a Wifi Wakelock
      // and the player itself.  Spin off a task to convert this "pause"
      // into a stop, soon.
      pause_task =
         new Later()
         {
            @Override
            public void later()
            {
               pause_task = null;
               stop();
            }
         }.start();

      player.pause();
      return done(State.STATE_PAUSE);
   }

   private int restart()
   {
      log("Restart: ", State.current());

      if ( player == null || State.is_stopped() )
         return play();

      // Always reset the volume.
      // There's something broken about the state model.
      // For example, we could be in state DUCK, then buffering starts, so
      // suddenly we're in state BUFFERING, although we're also still ducked.
      // The probelm is that one state is being used to model two different
      // things.  Until that's fixed, it is nevertheless always safe (??)
      // reset the volume on restart.
      //
      player.setVolume(1.0f, 1.0f);

      if ( State.is(State.STATE_PLAY) || State.is(State.STATE_BUFFER) )
         return done();

      if ( State.is(State.STATE_DUCK) )
         return done(State.STATE_PLAY);

      int focus = audio_manager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
      if ( focus != AudioManager.AUDIOFOCUS_REQUEST_GRANTED )
      {
         toast("Failed to acquire audio focus.");
         return done();
      }

      if ( pause_task != null )
         { pause_task.cancel(true); pause_task = null; }

      player.start();
      return done(State.STATE_PLAY);
   }

   /* ********************************************************************
    * Respond to click events from the notification.
    */

   private int click()
   {
      log("Click: ", State.current());

      if ( State.is(State.STATE_DISCONNECTED) )
      {
         stop();
         Notify.cancel();
         return done();
      }

      if ( State.is_playing() && ! isNetworkUrl() )
         return pause();

      if ( State.is_playing() )
      {
         stop();
         Notify.cancel();
         return done();
      }

      if ( player == null || State.is_stopped() )
         return play();

      if ( State.is(State.STATE_PAUSE) )
         return restart();

      log("Unhandled click: ", State.current());
      return done();
   }

   /* ********************************************************************
    * All onStartCommand() invocations end here...
    */

   private int done(String state)
   {
      if ( state != null )
         State.set_state(context, state, isNetworkUrl());

      return done();
   }

   private int done()
      { return START_NOT_STICKY; }

   /* ********************************************************************
    * Listeners...
    */

   @Override
   public void onBufferingUpdate(MediaPlayer player, int percent)
   {
      /*
      // Notifications of buffer state seem to be unreliable.
      if ( 0 <= percent && percent <= 100 )
         log("Buffering: ", ""+percent, "%"); 
         */
   }

   @Override
   public boolean onInfo(MediaPlayer player, int what, int extra)
   {
      switch (what)
      {
         case MediaPlayer.MEDIA_INFO_BUFFERING_START:
            State.set_state(context, State.STATE_BUFFER, isNetworkUrl());
            break;

         case MediaPlayer.MEDIA_INFO_BUFFERING_END:
            failure_ttl = initial_failure_ttl;
            State.set_state(context, State.STATE_PLAY, isNetworkUrl());
            break;
      }
      return true;
   }

   private Later start_buffering_task = null;

   private void start_buffering()
   {
      if ( start_buffering_task != null )
         start_buffering_task.cancel(true);

      // We'll give it 90 seconds for the stream to start.  Otherwise, we'll
      // declare an error.  onError() tries to restart, in some cases.
      start_buffering_task = (Later)
         new Later(90)
         {
            @Override
            public void later()
            {
               start_buffering_task = null;
               onError(null,0,0);
            }
         }.start();
   }

   private Later stop_soon_task = null;

   private void stop_soon()
   {
      if ( stop_soon_task != null )
         stop_soon_task.cancel(true);

      stop_soon_task = (Later)
         new Later(300)
         {
            @Override
            public void later()
            {
               stop_soon_task = null;
               stop();
            }
         }.start();
   }

   private void try_recover()
   {
      stop_soon();
      if ( isNetworkUrl() && 0 < failure_ttl )
      {
         failure_ttl -= 1;
         if ( connectivity.isConnected(context) )
            play();
         else
            connectivity.dropped_connection();
      }
   }

   // Waring:  onError is called, by start_buffering(), with null arguments.
   // Do not rely upon these arguments being meaningful.
   @Override
   public boolean onError(MediaPlayer player, int what, int extra)
   {
      log("Error: ", ""+what);

      State.set_state(context,State.STATE_ERROR, isNetworkUrl());
      try_recover(); // This calls stop_soon().

      // Returning true, here, prevents the onCompletionlistener from being called.
      return true;
   }

   /* ********************************************************************
    * On completion listener...
    */

   @Override
   public void onCompletion(MediaPlayer mp)
   {
      log("Completion: " + State.current());

      // We only enter STATE_COMPLETE for non-network URLs, and only if we
      // really were playing (so not, for example, if we are in STATE_ERROR, or
      // STATE_DISCONNECTED).  This simplifies connectivity management, in
      // Connectivity.java.
      log("onCompletion: isNetworkUrl: " + isNetworkUrl());
      if ( ! isNetworkUrl() && (State.is(State.STATE_PLAY) || State.is(State.STATE_DUCK)) )
         State.set_state(context, State.STATE_COMPLETE, isNetworkUrl());

      // Don't stay completed for long. stop(), soon, to free up resources.
      stop_soon();
   }

   /* ********************************************************************
    * Audio focus listeners...
    */

   @Override
   public void onAudioFocusChange(int change)
   {
      log("onAudioFocusChange: ", ""+change);

      if ( player != null )
         switch (change)
         {
            case AudioManager.AUDIOFOCUS_GAIN:
               log("Audiofocus_gain");
               restart();
               break;

            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
               log("Transient");
               // pause();
               // break;
               // Drop through.

            case AudioManager.AUDIOFOCUS_LOSS:
               log("Audiofocus_loss");
               pause();
               break;

            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
               log("Audiofocus_loss_transient_can_duck");
               duck("Audio focus lost, ducking...");
               break;
         }
   }

   /* ********************************************************************
    * Logging...
    */

   private void log(String... msg)
      { Logger.log(msg); }

   private void toast(String msg)
      { Logger.toast(msg); }

   /* ********************************************************************
    * Required abstract method...
    */

   @Override
   public IBinder onBind(Intent intent)
      { return null; }
}




Java Source Code List

org.smblott.intentradio.Build.java
org.smblott.intentradio.ClipButtons.java
org.smblott.intentradio.Clipper.java
org.smblott.intentradio.Connectivity.java
org.smblott.intentradio.CopyResource.java
org.smblott.intentradio.Counter.java
org.smblott.intentradio.HttpGetter.java
org.smblott.intentradio.IntentPlayer.java
org.smblott.intentradio.IntentRadio.java
org.smblott.intentradio.Intents.java
org.smblott.intentradio.Later.java
org.smblott.intentradio.Logger.java
org.smblott.intentradio.Metadata.java
org.smblott.intentradio.Notify.java
org.smblott.intentradio.Now.java
org.smblott.intentradio.PlaylistM3u.java
org.smblott.intentradio.PlaylistPls.java
org.smblott.intentradio.Playlist.java
org.smblott.intentradio.PreferenceActivity.java
org.smblott.intentradio.Prefs.java
org.smblott.intentradio.ReadRawTextFile.java
org.smblott.intentradio.State.java
org.smblott.intentradio.WifiLocker.java