/**
DR Radio 2 is developed by Jacob Nordfalk, Hanafi Mughrabi and Frederik Aagaard.
Some parts of the code are loosely based on Sveriges Radio Play for Android.
DR Radio 2 for Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as published by
the Free Software Foundation.
DR Radio 2 for Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
DR Radio 2 for Android. If not, see <http://www.gnu.org/licenses/>.
*/
package dk.dr.radio.afspilning;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnBufferingUpdateListener;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnInfoListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.media.MediaPlayer.OnSeekCompleteListener;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import dk.dr.radio.data.DRData;
import dk.dr.radio.diverse.AfspillerWidget;
import dk.dr.radio.diverse.App;
import dk.dr.radio.diverse.Log;
import dk.dr.radio.diverse.Opkaldshaandtering;
/**
* @author j
*/
public class Afspiller implements OnPreparedListener, OnSeekCompleteListener, OnCompletionListener, OnInfoListener, OnErrorListener, OnBufferingUpdateListener {
/** ID til notifikation i toppen. Skal bare være unikt og det samme altid */
//private static final int NOTIFIKATION_ID = 117;
/**
* Bruges fra widget til at kommunikere med servicen
*/
//public static final int WIDGET_HENT_INFO = 10;
public static final int WIDGET_START_ELLER_STOP = 11;
public static final int STATUS_STOPPET = 1;
public static final int STATUS_FORBINDER = 2;
public static final int STATUS_SPILLER = 3;
public int afspillerstatus = STATUS_STOPPET;
private MediaPlayer mediaPlayer;
private List<AfspillerListener> observatører = new ArrayList<AfspillerListener>();
public String kanalNavn;
public String kanalUrl;
//private Udsendelse aktuelUdsendelse;
//private String PROGRAMNAVN = "Radio";
private static void sætMediaPlayerLytter(MediaPlayer mediaPlayer, Afspiller lytter) {
mediaPlayer.setOnCompletionListener(lytter);
mediaPlayer.setOnErrorListener(lytter);
mediaPlayer.setOnInfoListener(lytter);
mediaPlayer.setOnPreparedListener(lytter);
mediaPlayer.setOnBufferingUpdateListener(lytter);
mediaPlayer.setOnSeekCompleteListener(lytter);
if (lytter != null && App.prefs.getBoolean(NØGLEholdSkærmTændt, false)) {
mediaPlayer.setWakeMode(App.instans, PowerManager.SCREEN_DIM_WAKE_LOCK);
//DRData.langToast("holdSkærmTændt");
}
}
//private Notification notification;
static final String NØGLEholdSkærmTændt = "holdSkærmTændt";
private WifiLock wifilock = null;
/**
* Forudsætter DRData er initialiseret
*/
public Afspiller() {
mediaPlayer = new MediaPlayer();
sætMediaPlayerLytter(mediaPlayer, this);
// Indlæs gamle værdier så vi har nogle...
// Fjernet. Skulle ikke være nødvendigt. Jacob 22/10-2011
// kanalNavn = p.getString("kanalNavn", "P1");
// kanalUrl = p.getString("kanalUrl", "rtsp://live-rtsp.dr.dk/rtplive/_definst_/Channel5_LQ.stream");
// Gem værdi hvis den ikke findes, sådan at indstillingsskærm viser det rigtige
if (!App.prefs.contains(NØGLEholdSkærmTændt)) {
// Xperia Play har brug for at holde skærmen tændt. Muligvis også andre....
boolean holdSkærmTændt = "R800i".equals(Build.MODEL);
App.prefs.edit().putBoolean(NØGLEholdSkærmTændt, holdSkærmTændt).commit();
}
wifilock = ((WifiManager) App.instans.getSystemService(Context.WIFI_SERVICE)).createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "DR Radio");
wifilock.setReferenceCounted(false);
Opkaldshaandtering opkaldshåndtering = new Opkaldshaandtering(this);
TelephonyManager tm = (TelephonyManager) App.instans.getSystemService(Context.TELEPHONY_SERVICE);
tm.listen(opkaldshåndtering, PhoneStateListener.LISTEN_CALL_STATE);
}
private int onErrorTæller;
private long onErrorTællerNultid;
public void startAfspilning() throws IOException {
Log.d("startAfspilning() " + kanalUrl);
onErrorTæller = 0;
onErrorTællerNultid = System.currentTimeMillis();
if (afspillerstatus == STATUS_STOPPET) {
//opdaterNotification();
// Start afspillerservicen så programmet ikke bliver lukket
// når det kører i baggrunden under afspilning
App.instans.startService(new Intent(App.instans, HoldAppIHukommelsenService.class).putExtra("kanalNavn", kanalNavn));
if (App.prefs.getBoolean("wifilås", true) && wifilock != null) try {
wifilock.acquire();
if (DRData.udvikling) App.langToast("wifilock.acquire()");
} catch (Exception e) {
Log.rapporterFejl(e);
} // TODO fjern try/catch
startAfspilningIntern();
AudioManager audioManager = (AudioManager) App.instans.getSystemService(Context.AUDIO_SERVICE);
// Skru op til 1/5 styrke hvis volumen er lavere end det
int max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int nu = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
if (nu < 1 * max / 5) {
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1 * max / 5, AudioManager.FLAG_SHOW_UI);
}
} else Log.d(" forkert status=" + afspillerstatus);
}
long setDataSourceTid = 0;
boolean setDataSourceLyd = false;
private String mpTils() {
AudioManager ar = (AudioManager) App.instans.getSystemService(App.AUDIO_SERVICE);
//return mediaPlayer.getCurrentPosition()+ "/"+mediaPlayer.getDuration() + " "+mediaPlayer.isPlaying()+ar.isMusicActive();
if (!setDataSourceLyd && ar.isMusicActive()) {
setDataSourceLyd = true;
App.langToast("Det tog "+(System.currentTimeMillis() - setDataSourceTid)/100/10.0+" sek før lyden kom");
}
return " "+mediaPlayer.isPlaying()+ar.isMusicActive()+" dt="+(System.currentTimeMillis() - setDataSourceTid)+"ms";
}
synchronized private void startAfspilningIntern() {
Log.d("Starter streaming fra " + kanalNavn);
Log.d("mediaPlayer.setDataSource( " + kanalUrl);
afspillerstatus = STATUS_FORBINDER;
sendOnAfspilningForbinder(-1);
opdaterWidgets();
handler.removeCallbacks(startAfspilningIntern);
// mediaPlayer.setDataSource() bør kaldes fra en baggrundstråd da det kan ske
// at den hænger under visse netværksforhold
new Thread() {
public void run() {
Log.d("mediaPlayer.setDataSource() start");
try {
setDataSourceTid = System.currentTimeMillis();
setDataSourceLyd = false;
mediaPlayer.setDataSource(kanalUrl);
Log.d("mediaPlayer.setDataSource() slut " + mpTils());
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.prepare();
Log.d("mediaPlayer.prepare() slut " + mpTils());
} catch (Exception ex) {
ex.printStackTrace();
//ex = new Exception("spiller "+kanalNavn+" "+kanalUrl, ex);
//Log.kritiskFejlStille(ex);
handler.post(new Runnable() {
public void run() { // Stop afspilleren fra forgrundstråden. Jacob 14/11
onError(mediaPlayer, 42, 42); // kalder stopAfspilning(); og forsøger igen senere og melder fejl til bruger efter 10 forsøg
}
});
}
}
}.start();
}
synchronized public void stopAfspilning() {
Log.d("AfspillerService stopAfspilning");
handler.removeCallbacks(startAfspilningIntern);
// Da mediaPlayer.reset() erfaringsmæssigt kan hænge i dette tilfælde afregistrerer vi
// alle lyttere og bruger en ny
final MediaPlayer gammelMediaPlayer = mediaPlayer;
sætMediaPlayerLytter(gammelMediaPlayer, null); // afregistrér alle lyttere
new Thread() {
@Override
public void run() {
try {
gammelMediaPlayer.stop();
Log.d("gammelMediaPlayer.release() start");
gammelMediaPlayer.release();
Log.d("gammelMediaPlayer.release() færdig");
} catch (Exception e) {
Log.rapporterFejl(e);
}
}
}.start();
mediaPlayer = new MediaPlayer();
sætMediaPlayerLytter(mediaPlayer, this); // registrér lyttere på den nye instans
afspillerstatus = STATUS_STOPPET;
opdaterWidgets();
//if (notification != null) notificationManager.cancelAll();
// Stop afspillerservicen
App.instans.stopService(new Intent(App.instans, HoldAppIHukommelsenService.class));
if (wifilock != null) try {
wifilock.release();
} catch (Exception e) {
Log.rapporterFejl(e);
} // TODO fjern try/catch
// Informer evt aktivitet der lytter
for (AfspillerListener observatør : observatører) {
observatør.onAfspilningStoppet();
}
}
/**
* Sætter notification i toppen af skærmen
* <p/>
* private void opdaterNotification() {
* if (notification == null) {
* notification = new Notification(R.drawable.notifikation_ikon, null, 0);
* <p/>
* // PendingIntent er til at pege på aktiviteten der skal startes hvis brugeren vælger notifikationen
* notification.contentIntent = PendingIntent.getActivity(DRData.appCtx, 0, new Intent(DRData.appCtx, Afspilning_akt.class), 0);
* notification.flags |= (Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT);
* }
* <p/>
* notification.setLatestEventInfo(DRData.appCtx, PROGRAMNAVN, kanalNavn, notification.contentIntent);
* notificationManager.notify(NOTIFIKATION_ID, notification);
* }
*/
public void addAfspillerListener(AfspillerListener lytter) {
if (!observatører.contains(lytter)) {
observatører.add(lytter);
// Informer lytteren om aktuel status
if (afspillerstatus == STATUS_FORBINDER) {
lytter.onAfspilningForbinder(-1);
} else if (afspillerstatus == STATUS_STOPPET) {
lytter.onAfspilningStoppet();
} else {
lytter.onAfspilningStartet();
}
}
}
public void removeAfspillerListener(AfspillerListener lytter) {
observatører.remove(lytter);
}
public void setKanal(String navn, String url) {
kanalNavn = navn;
kanalUrl = url;
// Fjernet. Skulle ikke være nødvendigt. Jacob 22/10-2011
/*
PreferenceManager.getDefaultSharedPreferences(DRData.appCtx).edit()
.putString("kanalNavn", kanalNavn)
.putString("kanalUrl", kanalUrl)
.commit();
*/
if ((afspillerstatus == STATUS_SPILLER) || (afspillerstatus == STATUS_FORBINDER)) {
stopAfspilning();
try {
startAfspilning();
} catch (Exception e) {
e.printStackTrace();
}
}
opdaterWidgets();
}
private void opdaterWidgets() {
AppWidgetManager mAppWidgetManager = AppWidgetManager.getInstance(App.instans);
int[] appWidgetId = mAppWidgetManager.getAppWidgetIds(new ComponentName(App.instans, AfspillerWidget.class));
for (int id : appWidgetId) {
AfspillerWidget.opdaterUdseende(App.instans, mAppWidgetManager, id);
}
}
public int getAfspillerstatus() {
return afspillerstatus;
}
//
// TILBAGEKALD FRA MEDIAPLAYER
//
public void onPrepared(MediaPlayer mp) {
Log.d("onPrepared "+mpTils());
afspillerstatus = STATUS_SPILLER; //No longer buffering
if (observatører != null) {
opdaterWidgets();
for (AfspillerListener observer : observatører) {
observer.onAfspilningStartet();
}
}
// Det ser ud til kaldet til start() kan tage lang tid på Android 4.1 Jelly Bean
// (i hvert fald på Samsung Galaxy S III), så vi kalder det i baggrunden
new Thread() {
public void run() {
Log.d("mediaPlayer.start() "+mpTils());
mediaPlayer.start();
Log.d("mediaPlayer.start() slut "+mpTils());
}
}.start();
}
public void onCompletion(MediaPlayer mp) {
Log.d("AfspillerService onCompletion!");
// Hvis forbindelsen mistes kommer der en onCompletion() og vi er derfor
// nødt til at genstarte, medmindre brugeren trykkede stop
if (afspillerstatus == STATUS_SPILLER) {
Log.d("Genstarter afspilning!");
mediaPlayer.stop();
// mediaPlayer.reset();
// Da mediaPlayer.reset() erfaringsmæssigt kan hænge i dette tilfælde afregistrerer vi
// alle lyttere og bruger en ny
final MediaPlayer gammelMediaPlayer = mediaPlayer;
sætMediaPlayerLytter(gammelMediaPlayer, null); // afregistrér alle lyttere
new Thread() {
public void run() {
Log.d("gammelMediaPlayer.release() start");
gammelMediaPlayer.release();
Log.d("gammelMediaPlayer.release() færdig");
}
}.start();
mediaPlayer = new MediaPlayer();
sætMediaPlayerLytter(mediaPlayer, this); // registrér lyttere på den nye instans
startAfspilningIntern();
}
}
public boolean onInfo(MediaPlayer mp, int hvad, int extra) {
//Log.d("onInfo(" + MedieafspillerInfo.infokodeTilStreng(hvad) + "(" + hvad + ") " + extra);
Log.d("onInfo(" + hvad + ") " + extra+ " "+mpTils());
return true;
}
Handler handler = new Handler();
Runnable startAfspilningIntern = new Runnable() {
public void run() {
try {
startAfspilningIntern();
} catch (Exception e) {
Log.rapporterFejl(e);
}
}
};
public boolean onError(MediaPlayer mp, int hvad, int extra) {
//Log.d("onError(" + MedieafspillerInfo.fejlkodeTilStreng(hvad) + "(" + hvad + ") " + extra+ " onErrorTæller="+onErrorTæller);
Log.d("onError(" + hvad + ") " + extra + " onErrorTæller=" + onErrorTæller);
if (Build.VERSION.SDK_INT >= 16 && hvad == MediaPlayer.MEDIA_ERROR_UNKNOWN) {
// Ignorer, da Samsung Galaxy SIII på Android 4.1 Jelly Bean
// sender denne fejl (onError(1) -110) men i øvrigt spiller fint videre!
return true;
}
// Iflg http://developer.android.com/guide/topics/media/index.html :
// "It's important to remember that when an error occurs, the MediaPlayer moves to the Error
// state and you must reset it before you can use it again."
if (afspillerstatus == STATUS_SPILLER || afspillerstatus == STATUS_FORBINDER) {
// Hvis der har været
// 1) færre end 10 fejl eller
// 2) der højest er 1 fejl pr 20 sekunder så prøv igen
long dt = System.currentTimeMillis() - onErrorTællerNultid;
if (onErrorTæller++ < (DRData.udvikling ? 2 : 10) || (dt / onErrorTæller > 20000)) {
mediaPlayer.stop();
mediaPlayer.reset();
// Vi venter længere og længere tid her
int n = onErrorTæller;
if (n > 11) n = 11;
int ventetid = 10 + 5 * (1 << n); // fra n=0:10 msek til n=10:5 sek til max n=11:10 sek
Log.d("Ventetid før vi prøver igen: " + ventetid + " n=" + n + " " + onErrorTæller);
handler.postDelayed(startAfspilningIntern, ventetid);
} else {
stopAfspilning(); // Vi giver op efter 10. forsøg
App.langToast("Beklager, kan ikke spille radio");
App.langToast("Prøv at vælge et andet format i indstillingerne");
}
} else {
mediaPlayer.reset();
}
return true;
}
private void sendOnAfspilningForbinder(int procent) {
for (AfspillerListener observer : observatører) {
observer.onAfspilningForbinder(procent);
}
}
public void onBufferingUpdate(MediaPlayer mp, int procent) {
//Log.d("Afspiller onBufferingUpdate : " + procent + "% - lyttere er "+observatører );
Log.d("Afspiller onBufferingUpdate : " + procent + " "+mpTils());
if (procent < -100) procent = -1; // Ignorér vilde tal
sendOnAfspilningForbinder(procent);
}
public void onSeekComplete(MediaPlayer mp) {
Log.d("AfspillerService onSeekComplete");
}
}
/* x86v10
02-20 16:24:06.846 1398-1398/dk.dr.radio D/DRRadio﹕ startAfspilning() http://live-icy.gss.dr.dk:8000/A/A03L.mp3
02-20 16:24:06.856 1398-1398/dk.dr.radio D/DRRadio﹕ Starter streaming fra DR P1
02-20 16:24:06.856 1398-1398/dk.dr.radio D/DRRadio﹕ mediaPlayer.setDataSource( http://live-icy.gss.dr.dk:8000/A/A03L.mp3
02-20 16:24:06.856 1398-1398/dk.dr.radio D/DRRadio﹕ Forbinder...
02-20 16:24:06.856 1398-1430/dk.dr.radio D/DRRadio﹕ mediaPlayer.setDataSource() start
02-20 16:24:06.856 1398-1430/dk.dr.radio D/DRRadio﹕ mediaPlayer.setDataSource() slut
02-20 16:24:06.856 1398-1398/dk.dr.radio D/DRRadio﹕ AfspillerService onStartCommand(Intent { cmp=dk.dr.radio/.afspilning.HoldAppIHukommelsenService (has extras) } 2 1
02-20 16:24:24.546 1398-1398/dk.dr.radio D/DRRadio﹕ Afspiller onBufferingUpdate : -2147483648 0
02-20 16:24:24.546 1398-1398/dk.dr.radio D/DRRadio﹕ Forbinder...
02-20 16:24:24.546 1398-1398/dk.dr.radio D/DRRadio﹕ onPrepared
02-20 16:24:24.546 1398-1398/dk.dr.radio D/DRRadio﹕ Afspiller
02-20 16:24:24.546 1398-1433/dk.dr.radio D/DRRadio﹕ mediaPlayer.start()
02-20 16:24:24.556 1398-1433/dk.dr.radio D/DRRadio﹕ mediaPlayer.start() slut
02-20 16:24:25.556 1398-1398/dk.dr.radio D/DRRadio﹕ Afspiller onBufferingUpdate : -2147483648 1437
02-20 16:24:25.556 1398-1398/dk.dr.radio D/DRRadio﹕ [ 02-20 16:24:26.566 1398:0x576 D/DRRadio ]
Afspiller onBufferingUpdate : -2147483648 2455
*/