Themabewertung:
  • 1 Bewertung(en) - 5 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
Ein minimales Internetradio für alte und neue (Android) Smartphones
#1
Hallo zusammen,

auf Grund meines Threads "Ein minimales Internetradio für alte und neue Raspberrys" (https://radio-bastler.de/forum/showthread.php?tid=11484), erhielt ich per eMail die Anfrage ob soetwas nicht auch mit alten Android Smartphones möglich wäre. Die alten Geräte seinen heute einfach zu schwach um aktuelle Internetradio-Apps wirklich sinnig betreiben zu können. So ein altes Smartphone könnte ja, wenn es hier auch ein so minimales "iRadio" gebe, für die Zuspielung einer Internetradiostation zu einem Modulator dienen oder direkt über den Toneingang eines Radios betrieben werden. Der Mailschreiber wollte sogar ein altes Smartphone zerlegen und damit einen Radioumbau vornehmen. Zitat: "...ist doch auch nur ein Raspberry mit aufgeklebtem Touchscreen..." .

So ganz Unrecht hat der Schreiber nicht. Auch bei Android-Smartphones läuft in der Regel ein ARM-SoC mit 1 bis 8 Kernen, irgendwas ab 256 MB Ram und ganz genau ein Linux drauf.

Was die Ist-Situation mit den Apps angeht, so habe ich mal einen Selbsttest unternommen und gängige Radioplayer auf einem Uralt-Smartphone mit einem(!) Prozessorkern in der Leistungsklasse eine Raspberry Model A getestet.

Es stimmt, heutige Apps sind nicht mehr nutzbar auf solchen alten Kisten. Der Grund: Werbung, Werbung, Werbung, ..... Vor lauter Werbung kommen die Apps nicht dazu Ihre eigentliche Aufgabe zu erledigen: einfach einen Radiostream wiederzugeben!


.jpg   Werbung1.JPG (Größe: 45,5 KB / Downloads: 715)
Schon beim Appstart werde ich "angemacht" und nach einem Liebesbekenntnis gefragt! Aber: So leicht bin ich nicht zu haben.


.jpg   Werbung2.JPG (Größe: 51,38 KB / Downloads: 706)
Natürlich gibt es von jeder App auch eine "Premium"-Version, die man "Riskfree" testen kann. Was für ein "Risk" geht man denn da ein?  
Allein das Laden der Benutzeroberfläche und dieses Werbebalkens benötigt eine Minute. Wir schauen auf die Uhr des Telefons! Musik ist noch keine zu hören!


.jpg   Werbung3.JPG (Größe: 52,2 KB / Downloads: 717)
Wieder eine Minute später, endlich läuft das Radio an, ganz Langsam und mit vielen Aussetzern. Der Prozessor ist einfach überfordert.


Probieren wir eine andere aktuelle App.


.jpg   Werbung4.JPG (Größe: 57,45 KB / Downloads: 709)

Auch hier wieder unerträgliche Ladezeiten und Werbung bis zum Abwinken! Das Umschalten des Senders ist wegen der überfrachteten Werbung leistungstechnisch nicht möglich. Das Telefon hat noch 5 Minuten damit zu verbringen, den Job des Wechseln eines Stream zu verarbeiten.
Ich gebe an dieser Stelle auf, bei anderen noch getesteten Apps kommt man zum gleichen Ergebnis.

Was ist eigentlich so schwierig daran, einfach in einer App einen Stream zu laden und die Ausgabe im Soundsystem des Android-OS zu machen?

Ich überfliege die Systemdokumentation und Entwicklerinfos zu Android und beschließe hier ein ähnliches "abgespecktes" Vorbild zu kreieren, wie mit dem iRadio für Ur-Raspberrys.

Zunächst lade ich das Andoid Studio, die kostenlose Entwicklungsumgebung für Apps von Google. Diese Entwicklungsumgebung gibt es für Windows-, Linux- und Mac-Systeme: https://developer.android.com/studio/

Dann schreibe ich eine kleine App mit ganz minimalistischer Benutzeroberfläche und relativ wenig Code für genau die oben definierte Aufgabe.

Hier der Quellcode in Version 0.0001 ;-) : https://mega.nz/#!OKxkmIwa!Vf4TvLcsQ57Ix...1d32G4O9RY

Eine fertig gebaute APK-Datei liegt in /iRadio/app/build/outputs/apk/debug/

   

Ein wunderbarer Codeeditor auf Basis der OpenSource-Version von IntelliJ der Firma JetBrains. Geschrieben in Java, aber unsere Entwicklungsmaschine (!nicht das Zielsystem!) hat ja genügend Power um solche Sachen laufen zu lassen. Braucht sie auch, denn die gesammte Android-Toolchain basiert praktisch auf viel Java und Kotlin nur weniges ist in C und C++ direkt kodiert. Auch wir werden unsere App für das Zielsystem in Java programmieren, das wird aber so wenig sein das der kleine Prozessor keine Probleme damit haben wird.

   

Unsere GUI können wir per Drag & Drop zeichnen, sie entspricht praktisch der minimalistischen FLTK-GUI des iRadios auf dem Raspberry Pi. Wer will kann sich mit den Sourcen oben ja eine eigene kleine (und optisch schönere) GUI erstellen.


   

Zuletzt noch das Manifest für unsere App, hier werden zum Beispiel die "Bedürfnisse" der App beim Betriebssystem definiert. Welche Dienste des OS möchte die App nutzen, welche Hardwarerechte braucht die App usw.


Schauen wir uns nun mal den eigentlichen Quellcode an:


Code:
package de.weiro.iradio;

import android.content.Context;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.wifi.WifiManager;
import android.os.PowerManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.content.pm.ActivityInfo;
import android.os.Environment;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;


public class MainActivity extends AppCompatActivity {

   Button btn_play, btn_next, btn_prev, btn_exit;
   TextView textView;
   MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever();
   MediaPlayer mediaPlayer;

   Thread radioDataThread;
   PowerManager pm;
   PowerManager.WakeLock wakeLock;
   WifiManager.WifiLock wifiLock;

   static boolean started = false;
   URL metaDataURL;

   int index = 1;

   List<String> streamURLs = new ArrayList<String>();

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

       File folderDownload = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
       File filePlaylist = new File(folderDownload, "playlist.m3u");

       //File file = new File("/storage/sdcard0/Download/playlist.m3u");
       Log.e("Pfad zur Playlist",filePlaylist.toString());
       if (!filePlaylist.canRead() || !filePlaylist.isFile()) {
           Log.e("Senderliste:" , "keine gefunden, nutze Defaultliste!");
          streamURLs.add("http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio2_mf_p");
          streamURLs.add("http://st02.dlf.de/dlf/02/128/mp3/stream.mp3");
          streamURLs.add("http://str0.creacast.com/hitradio_ohr_thema1");
          streamURLs.add("http://stream.srg-ssr.ch/m/drs4news/aacp_96");
          streamURLs.add("http://stream1.evasionfm.com/Chante_France");
          }

          BufferedReader in = null;

       try {

           //in = new BufferedReader(new FileReader("/storage/sdcard0/Download/playlist.m3u"));
           in = new BufferedReader(new FileReader(filePlaylist.toString()));
           String zeile = null;
           while ((zeile = in.readLine()) != null) {
               streamURLs.add(zeile);
               Log.e("Senderliste lesen:" , zeile);
           }

       } catch (IOException e) {
           e.printStackTrace();
       } finally {
           if (in != null)
               try {
                   in.close();
               } catch (IOException e) {
               }
       }


       pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
       wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "myWakeLock");

       wifiLock = ((WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE))
       .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF , "myWiFiLock");


       mediaPlayer = new MediaPlayer();
       mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
       mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
       mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
           @Override
           public void onPrepared(MediaPlayer mp) {
               mp.start();
               started = true;
               btn_play.setText("PAUSE");
           }
       });

       setContentView(R.layout.activity_main);

       btn_next = (Button) findViewById(R.id.button_next);
       btn_next.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               if (index < (streamURLs.size()-1) )
                   index++;
               else
                   index = 0;

               switchProgram();
           }
       });

       btn_prev = (Button) findViewById(R.id.button_prev);
       btn_prev.setOnClickListener(new View.OnClickListener()
       {
           @Override
           public void onClick(View v) {
               if (index == 0)
                   index = streamURLs.size() - 1;
               else
                   index--;

               switchProgram();
           }
       });


       btn_play = (Button) findViewById(R.id.button_play);
       btn_play.setText("LADE");
       btn_play.setOnClickListener(new View.OnClickListener()
       {
           @Override
           public void onClick(View view) {
               if (started) {
                   started = false;
                   mediaPlayer.pause();
                   btn_play.setText("PLAY");
               } else {
                   started = true;
                   mediaPlayer.start();
                   btn_play.setText("PAUSE");
               }
           }
       });

       btn_exit = (Button) findViewById(R.id.button_exit);
       btn_exit.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               finish();
           }
       });


       textView = (TextView) findViewById(R.id.textView);
       textView.setTextColor(Color.GREEN);

       wifiLock.acquire();
       wakeLock.acquire();

       try
       {
           mediaPlayer.reset();
           mediaPlayer.setDataSource(streamURLs.get(index));
           mediaPlayer.prepareAsync();
       } catch (IOException e) {
           e.printStackTrace();
       }

       try {
           metaDataURL = new URL(streamURLs.get(index));
       }
       catch (MalformedURLException ex) {
           ex.printStackTrace();
       }

       radioDataThread = new RadioDataThread();
       radioDataThread.start();
   }


   public class RadioDataThread extends Thread {
       ParsingHeaderData streaming;
       ParsingHeaderData.TrackData trackData;

       public void run() {
           while (true) {
               try {
                   streaming = new ParsingHeaderData();
                   trackData = streaming.getTrackDetails(metaDataURL);

                   textView.setText("Sender:" + trackData.channel + "\nArtist/Titel:" + trackData.artist + " - " + trackData.title +
                   "\nGenre:" + trackData.genre + "\nAudioinfos: " +trackData.audioinfo);

                   getWindow().getDecorView().findViewById(android.R.id.content).postInvalidate();
                   Thread.sleep(1000);


               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
       }

   }


   @Override
   protected void onDestroy() {
       super.onDestroy();

       if (mediaPlayer != null) {
           mediaPlayer.stop();
           mediaPlayer.release();
       }

       if (wakeLock != null) {
           if (wakeLock.isHeld()) {
               wakeLock.release();
           }
       }
       if (wifiLock != null) {
           if (wifiLock.isHeld()) {
               wifiLock.release();
           }
       }
   }


   protected void switchProgram() {
       try {
           mediaPlayer.stop();
           mediaPlayer.reset();
           mediaPlayer.setDataSource(streamURLs.get(index));
           mediaPlayer.prepareAsync();

           try {
               metaDataURL = new URL(streamURLs.get(index));
           }
           catch (MalformedURLException ex) {
               ex.printStackTrace();
           }

       } catch (IOException e) {
           e.printStackTrace();
       }

   }

}


Zunächst importieren wir mal alle Bibliotheken die wir benötigen.


Zitat:import android.content.Context;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaMetadataRetriever;
import android.net.wifi.WifiManager;
import android.os.PowerManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.content.pm.ActivityInfo;
import android.os.Environment;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;


Danach startet unsere Hauptklasse "MainActivity", vereinfacht gesagt die main()-Methode in einem C/C++ Programm. Der Rahmen des Programms wird von einem Projektwizzard im Android Studio schon generiert.

Zitat:
Code:
public class MainActivity extends AppCompatActivity {


Danach legen wir erstmal ein paar Variablen/Objekte fest.

Zitat:Button btn_play, btn_next, btn_prev, btn_exit;
TextView textView;

MediaPlayer mediaPlayer;

Thread radioDataThread;
PowerManager pm;
PowerManager.WakeLock wakeLock;
WifiManager.WifiLock wifiLock;

static boolean started = false;
URL metaDataURL;

int index = 1;

List<String> streamURLs = new ArrayList<String>();

Eigentlich klar, hinter btn_play, btn_next, .... stecken unsere Bedienknöpfe von der Benutzeroberfläche.
TextView: Ist das GUI-Objekt wo später der Sendername, Titelname usw. erscheinen werden.
Mediaplayer: Das ist unser VLC-Pendant. Zwar gibt es VLC auch für Android, aber wir nehmen einfach den betriebssystemeigenen Mediaplayer, der ist fast genauso gut und man spart Ressourcen auf der Zielplatform.

radioDataThread: Dahinter verbirgt sich nachher der Thread der ständig in den Datenstrom schaut, die Metadaten (Sendername, Titelname usw.) holt und unsere Senderanzeige aktualisieren wird.

PowerManager und WifiManager: Mit diesen beiden Objekten erstellen wir später in unserer App eine Softwaresperre. Das Betriebsystem darf während die App läuft nicht das Wifi abschalten (klar warum) und auch nicht in den Schlafmodus gehen!

started: einfach ein Statusflag

URL metaDataURL: Da der Mediaplayer selbst keine Metadaten aus dem Datenstrom zieht, so wie es vlc macht, wird hier nochmal für eine anderes Objekt  global die URL gehalten auf die unser Mediaplayer zeigt. Das kann man sicher auch noch rausnehmen und über Methoden realisieren, ich habe es jetzt auf die Schnelle so gemacht.

index: Ist einfach ein Index auf die aktuelle Stelle in der Senderliste, hier vorinitialisiert mit 1.

streamURLs: Unsere Senderliste als ArrayList.


Weiter gehts:

Zitat:@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

   File folderDownload = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
   File filePlaylist = new File(folderDownload, "playlist.m3u");

   //File file = new File("/storage/sdcard0/Download/playlist.m3u");
   Log.e("Pfad zur Playlist",filePlaylist.toString());
   if (!filePlaylist.canRead() || !filePlaylist.isFile()) {
       Log.e("Senderliste:" , "keine gefunden, nutze Defaultliste!");
      streamURLs.add("http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio2_mf_p");
      streamURLs.add("http://st02.dlf.de/dlf/02/128/mp3/stream.mp3");
      streamURLs.add("http://str0.creacast.com/hitradio_ohr_thema1");
      streamURLs.add("http://stream.srg-ssr.ch/m/drs4news/aacp_96");
      streamURLs.add("http://stream1.evasionfm.com/Chante_France");
      }

      BufferedReader in = null;

   try {

       //in = new BufferedReader(new FileReader("/storage/sdcard0/Download/playlist.m3u"));
       in = new BufferedReader(new FileReader(filePlaylist.toString()));
       String zeile = null;
       while ((zeile = in.readLine()) != null) {
           streamURLs.add(zeile);
           Log.e("Senderliste lesen:" , zeile);
       }

   } catch (IOException e) {
       e.printStackTrace();
   } finally {
       if (in != null)
           try {
               in.close();
           } catch (IOException e) {
           }
   }


Wir überschreiben die onCreate - Methode mit unserer eigenen Variante. Diese Methode wird immer beim App-Start (also beim onCreate) ausgeführt.

Mit   super.onCreate(savedInstanceState);
   this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
rufen wir die onCreate - Methode der Oberklasse auf, danach legen wir fest das unsere App immer im Breitformat (Landscape) laufen soll.


Zitat:   File folderDownload = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
   File filePlaylist = new File(folderDownload, "playlist.m3u");

   //File file = new File("/storage/sdcard0/Download/playlist.m3u");
   Log.e("Pfad zur Playlist",filePlaylist.toString());


Wie beim Pimoroni- und auch beim iRadio-Paket wollen wir eine Sendeliste in ein Verzeichnis legen können. Das Verzeichnis soll der Download-Ordner des
jeweiligen Androidsystems sein. In der Variable folderDownload speichern wird zunächst das Ergebnis der Anfrage nach dem Systempfad zum Downloadordner ab.
In filePlaylist speichern wir dann den gesammten Pfad inklusive Dateinamen der Senderliste ab. Die Senderliste soll wie bei den anderen Internetradios playlist.m3u sein und diese
Senderliste ist 1:1 aufgebaut, d.h. direkt vom Pimoroni oder iRadio nutzbar. Über Log.e schicken wir den ermittelten Pfad lediglich nochmal in die Debugausgaben.


Zitat:  if (!filePlaylist.canRead() || !filePlaylist.isFile()) {
       Log.e("Senderliste:" , "keine gefunden, nutze Defaultliste!");
      streamURLs.add("http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio2_mf_p");
      streamURLs.add("http://st02.dlf.de/dlf/02/128/mp3/stream.mp3");
      streamURLs.add("http://str0.creacast.com/hitradio_ohr_thema1");
      streamURLs.add("http://stream.srg-ssr.ch/m/drs4news/aacp_96");
      streamURLs.add("http://stream1.evasionfm.com/Chante_France");
      }

In der if-Anweisung schauen wir nun nach ob die Senderliste im Dateisystem existiert und ob diese auch lesbar ist. Ist das nicht der Fall, dann wird der Vorfall in die Debugausgabe geschrieben und eine interne Standardliste mit 5 Sendern erzeugt. Hier wären natürlich auch andere Behandlungen dieses Vorfalls denkbar, App mit Meldung beenden oder oder oder...



Zitat:   try {

       //in = new BufferedReader(new FileReader("/storage/sdcard0/Download/playlist.m3u"));
       in = new BufferedReader(new FileReader(filePlaylist.toString()));
       String zeile = null;
       while ((zeile = in.readLine()) != null) {
           streamURLs.add(zeile);
           Log.e("Senderliste lesen:" , zeile);
       }

   } catch (IOException e) {
       e.printStackTrace();
   } finally {
       if (in != null)
           try {
               in.close();
           } catch (IOException e) {
           }
   }

Wenn eine Senderliste im Dateisystem gefunden werden konnte, so lesen wir diese hier Zeilenweise aus und speichern jede Streamadresse (also jede Zeile) in der oben schon gezeigten
ArrayList (streamURLs) ab.  Im catch-Zweig lassen wir uns im Fehlerfall etwas in die Debugausgabe schreiben, im finally-Zweig schließen wir die Datei nach dem Auslesen wieder.

Zitat:pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "myWakeLock");

wifiLock = ((WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF , "myWiFiLock");

Hier holen wir uns nun die Referenzen vom PowerManager und des WifiManagers ins Programm. Die Softwaresperren sind noch nicht aktiv!


Zitat:mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
   @Override
   public void onPrepared(MediaPlayer mp) {
       mp.start();
       started = true;
       btn_play.setText("PAUSE");
   }
});

Wir erzeugen uns jetzt einen neuen MediaPlayer und stellen Ihn darauf ein Internetradio zu empfangen.


Zitat:btn_next = (Button) findViewById(R.id.button_next);
btn_next.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       if (index < (streamURLs.size()-1) )
           index++;
       else
           index = 0;

       switchProgram();
   }
})

Die Variable btn_next vom Typ Button bekommt nun ihre Verbindung zum eigentlichen Knopf auf der Benutzeroberfläche. Gleichzeitig installieren wir an diesem Knopf einen "Listener". Dieser "Listener" hört auf "OnClick"-Events, also wenn auf den Knopf gedrückt wird. Was genau passiert dann? Es wird geschaut wie groß die Sendeliste ist und ob die Indexvariable die den aktuell eingestellten Sender repräsentiert bereits auf der letzten Position der Senderliste angekommen ist. Ist das nicht der Fall, dann wird der Index um eins erhöht, ist der Index bereits am Ende der Programmliste angekommen, wird der Index wieder auf die erste Position zurückgesetzt. Danach wird dann noch die Funktion "switchProgram" aufgerufen, diese macht die eigentliche Umschaltung am Mediaplayer, mehr dazu später.

Analog erfolgt im Programmcode jetzt die Verknüpfung mit den anderen Knöpfen auf der GUI. Braucht es dazu eine Erklärung?


Zitat:textView = (TextView) findViewById(R.id.textView);
textView.setTextColor(Color.GREEN);

Jetzt Verknüpfen wir unser GUI-Element zur Anzeige der Senderinfos mit der Variablen textView. Gleichzeitig geben wir an, das als Schriftfarbe für die Senderinfos "grün" gewählt wird.


Zitat:wifiLock.acquire();
wakeLock.acquire();

Gleich kommen wir in die heiße Phase. Unsere App meldet an, das wir ab jetzt kein Powersaving haben wollen und auch Wifi nicht abgeschaltet werden soll.


Zitat:try
{
   mediaPlayer.reset();
   mediaPlayer.setDataSource(streamURLs.get(index));
   mediaPlayer.prepareAsync();
} catch (IOException e) {
   e.printStackTrace();
}

try {
   metaDataURL = new URL(streamURLs.get(index));
}
catch (MalformedURLException ex) {
   ex.printStackTrace();
}

Im ersten try-Zweig versuchen wir jetzt unseren Mediaplayer zu starten. Wir übergeben Ihm die Senderadresse aus der Senderliste. Welche? Die Adrese auf die index zeigt.
Geht etwas schief, werden wir es in der Debugausgabe sehen.
Im zweiten try-Zweig übergeben wir die Senderadresse an die Variable metaDataURL. Diesmal aber nicht als String sondern als URL-Object. Auch hier, geht was schief, lesen wir es im Debugger des Android Studios.

Zitat:radioDataThread = new RadioDataThread();
radioDataThread.start();

Unser Mediaplayer läuft bereits, was brauchen wir noch? Wir erzeugen einen neuen Thread, eine Instanz der Inner-Class RadioDataThread.
Dieser Thread soll nun ständig im Datenstrom nach Metadaten suchen und uns periodisch im textView anzeigen.
Wie die Klasse des RadioDataThreads aussieht, nun das sehen wir direkt im Anschluß im Quellcode.


Zitat:public class RadioDataThread extends Thread {
   ParsingHeaderData streaming;
   ParsingHeaderData.TrackData trackData;

   public void run() {
       while (true) {
           try {
               streaming = new ParsingHeaderData();
               trackData = streaming.getTrackDetails(metaDataURL);

               textView.setText("Sender:" + trackData.channel + "\nArtist/Titel:" + trackData.artist + " - " + trackData.title +
               "\nGenre:" + trackData.genre + "\nAudioinfos: " +trackData.audioinfo);

               getWindow().getDecorView().findViewById(android.R.id.content).postInvalidate();
               Thread.sleep(1000);


           } catch (Exception e) {
               e.printStackTrace();
           }
       }
   }

}

Die Klasse RadioDataThread wird von der Systemklasse Thread abgeleitet und erbt damit alle Eigenschaften dieser. Ein AsyncTask ginge sicher auch.
Das was in diesem Thread gemacht werden soll, steht in der Methode run() des Threads. Hier wird in einer Endlosschleife nach den Metadaten im Datenstrom gesucht. Das übernehmen die Instanzen der Klassen ParsingHeaderData und TrackData. Beide sindin der Datei ParsingHeaderData.java definiert. Diese Klassen habe ich aus dem Netz fertig übernommen und für meine Zwecke leicht modifiziert. Ein Versuch an die Metadaten mit dem Systemeigenen Metacrawler zu kommen, verlief vorerst leider negativ. Ich konnte zwar technische Daten wie Bitrate, Samplerate etc. extrahieren, Streamtitle usw. dagegen blieben immer leer, deshalb erstmal der Weg über eine Fremdklasse. Irgendwann finde ich die Zeit mir das mal genauer anzuschauen und nach dem Problem zu suchen.

Was passiert noch im Programm?


Zitat:@Override
protected void onDestroy() {
   super.onDestroy();

   if (mediaPlayer != null) {
       mediaPlayer.stop();
       mediaPlayer.release();
   }

   if (wakeLock != null) {
       if (wakeLock.isHeld()) {
           wakeLock.release();
       }
   }
   if (wifiLock != null) {
       if (wifiLock.isHeld()) {
           wifiLock.release();
       }
   }
}


Nun wir überschreiben noch die Methode onDestroy() durch unsere eigene Version. Das ist das Gegenstück zur onCreate und wird bei Beendigung der App aufgerufen.
Hier räumen wir auf. Wir "entlassen" unseren Mediaplayer und die beiden Softwaresperren heben wir auch wieder auf.

Zitat:protected void switchProgram() {
   try {
       mediaPlayer.stop();
       mediaPlayer.reset();
       mediaPlayer.setDataSource(streamURLs.get(index));
       mediaPlayer.prepareAsync();

       try {
           metaDataURL = new URL(streamURLs.get(index));
       }
       catch (MalformedURLException ex) {
           ex.printStackTrace();
       }

   } catch (IOException e) {
       e.printStackTrace();
   }

}

In der switchProgram()-Methode reagieren wir auf die Änderung der Variable index durch die beiden onClick-Listener weiter oben. Wir stoppen den Mediaplayer, schicken Ihm die neue Senderadresse aus der Senderliste und bereiten Ihn auf den Neustart mit neuer Sender-URL vor. Auch für den "Parser" der Metadaten setzen wir die Adresse neu. Falls etwas schief geht, lesen wir das wieder im Debugger.

Das wäre also das Programm zu unserem kleinen Internetradio. Keine Werbung und nichts unnötiges. Wie bekommen wir das Programm jetzt auf das alte Handy, Tablet ,....?

Zunächst müssen wir für unser Android Zielsystem und falls unsere Entwicklungsmaschine eine Windowsmaschine ist, einen vom Hersteller verfügbaren ADB-Treiber installieren.
Google hat dafür schon eine Tabelle vorbereitet: https://developer.android.com/studio/run/oem-usb
Ist das Android-Gerät nicht dabei, hilft uns sicher Google weiter.

Ist der Treiber installiert, müssen wir auf unserem Android Zielsystem noch die Entwickleroptionen aktivieren. In der Regel geht man so vor:


.jpg   Entwicklermodus1.JPG (Größe: 56,23 KB / Downloads: 697)

Man geht bei Android über "Einstellungen" zu den "Geräteinformationen". Dort scrollt man runter bis zur "Buildnummer". Auch wenn dieser Menüpunkt deaktiviert aussieht, mehrfach (ich glaube es waren 7 mal) anklicken!

Danach findet man in den "Einstellungen" den sonst versteckten Menüpunkt "Entwickleroptionen"


.jpg   Entwicklermodus2a.jpg (Größe: 69,11 KB / Downloads: 695)

Jetzt aktiviert man jene Entwickleroptionen und wählt dabei praktischerweise noch folgende (selbsterklärenden) Einstellungen aus:


.jpg   Entwicklermodus3.JPG (Größe: 60,18 KB / Downloads: 693)
.jpg   Entwicklermodus4.JPG (Größe: 59,59 KB / Downloads: 692)

Nun ist das Android Zielsystem eingerichtet. Ist das Android-Smartphone oder Tablet bereits per USB-Ladekabel mit dem Entwicklungsrechner verbunden, geht es im Android Studio weiter.

   

Wir starten unsere Kompilierung durch klicken auf den unter 1 im Bild gezeigten Pfeil. Im sich danach öffnenden Fenster wählt man sein per USB verbundenes Android Zielsystem aus.
Alternativ wäre auch ein emuliertes Android-System möglich. So eine Emulation setzt aber wirklich eine leistungsfähige Mehrkern-Entwicklungsmaschine voraus, 8 GB RAM ist wirklich unterste Grenze, mit einem 16+x GB System und 4 Kernen macht es dann richtig Spaß auch in der Emulation zu arbeiten. Wir aber begnügen uns hier mit richtiger Android Hardware und brauchen keine Emulation.

Ein abschließender Klick auf "Okay" startet dann den Build-Vorgang und das Aufspielen und Starten der App auf dem Android-Zielsystem.
Das Ergebnis:

   

Eine werbefreie Internetradio-App nach dem iRadio-Vorbild, die nur genau das macht, was sie auch machen soll. Brauchten fertige Apps noch 3 Minuten zum Starten des Internetradios und 5 Minuten zum Umschalten des Senders, sind es jetzt 4 Sekunden Startzeit und weitere 5 Sekunden bis der Stream gepuffert ist und störungsfrei läuft. Das Umschalten gelingt innerhalb von Sekunden.

Da das Android-Zielsystem an der Entwicklermaschine gleich auch noch als USB-Speicherstick gemountet wird, lässt sich die Playlist meiner anderen Internetradios direkt per Dateibrowser übertragen.


.jpg   Filesys1.JPG (Größe: 35,18 KB / Downloads: 685)


.jpg   Filesys2.JPG (Größe: 16,9 KB / Downloads: 685)

Ich hoffe Euch hat der Ausflug in die Android-Welt gefallen, die Fragen des Mailers wurden beantwortet und einige von Euch werden motiviert auch mal in dieser Richtung aktiv zu werden. Entweder auf dem Raspberry (auf dem auch Android laufen kann) oder direkt bei der Wiederbelebung und beim Umbau alter Smartphones zu  Radios.

Gruß
Bernhard45
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#2
Immer wieder beeindruckend.
Hast Du aber mal die Finger vom Playstore gelassen und Dich lieber bei FDroid umgeschaut? Da gibts nämlich die guten Apps, klein, sparsam, werbefrei, open source, uswusw.
Guck Dir dort mal das "Webradio" an! Ich hab kein uraltes lauffähiges Handy aber ich kann mir gut vorstellen, daß die App "tut".

PS: Ich bin sehr überzeugt, daß mittlerweile in der Mehrzahl der halbwegs erfolgreichen Apps ungefragt ein Miner werkelt...
Gruß,
Uli
Zitieren
#3
Danke, Bernhard,
Darauf habe ich gewartet. Ich versuche es nächste Woche mit meinem alten Samsung.
Gruß,
Ivan
Zitieren
#4
Hallo reinersbg,

ich schicke Dir das Muster einer playlist.m3u mal hier über das Forum zu, per PN geht es ja nicht.

Du musst die Datei playlist.m3u.zip im Anhang  aber noch in playlist.m3u umbenennen, da die Forensoftware aktuell kein Hochladen von .m3u Dateien zulässt.

Wie bereits er PN besprochen, muss die playlist.m3u in den "Download" Ordner deines Smartphones kopiert werden. Nach einem Neustart der iRadio-App wird dann die Senderliste geladen!

Nochmal zum Aufbau der playlist.m3u : Die Datei entspricht exakt dem Playlist-Format welches wir auch im iRadio für Raspberrys benutzen (https://github.com/BM45/iRadio). Pro Zeile in der Datei muss die Internetadresse einer Radiostation stehen.

In der Datei im Anhang, die als Mustervorlage genutzt werden kann, ist das folgender Inhalt:

Zitat:http://relay4.slayradio.org:8200/
http://allstream.rainwave.cc:8000/all.mp3
http://tx.sharp-stream.com/icecast.php?i=planetrock.mp3
http://s1.viastreaming.net:8000
http://sc6.radiocaroline.net:8040/listen.pls
http://sc6.radiocaroline.net:10558/listen.pls
http://www.listenlive.eu/bbcradio2.m3u
http://www.listenlive.eu/bbcradio4.m3u
http://listen.181fm.com/181-uktop40_128k.mp3
http://listen.181fm.com/181-90scountry_128k.mp3
http://listen.181fm.com/181-oldschool_128k.mp3
http://listen.181fm.com/181-90sdance_128k.mp3
http://listen.181fm.com/181-chilled_128k.mp3
http://listen.181fm.com/181-classical_128k.mp3

Wenn Du die Datei mit einem Editor auf einem Windows-Betriebssystem editieren willst, beachte bitte beim Speichern, dass das Betriebssystem nicht einfach eine weitere Dateiendung an den Dateinamen ranghängt (zum Beispiel playlist.m3u.txt)! Dann wird die Senderliste natürlich von der App auf dem Smartphone nicht gefunden.

Ich hoffe die Sache mit der Senderliste  (auch für andere Nutzer) halbwegs transparent dargestellt zu haben. Wenn es Probleme gibt, schreibe ruhig hier weiter.

Gruß
Bernhard


Angehängte Dateien
.zip   playlist.m3u.zip (Größe: 611 Bytes / Downloads: 9)
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#5
Hallo Bernhard
habe alles so gemacht wie beschrieben App findet keine Playlist.
kann es sein das die App nicht geht.Kannst Du mir die apk der App neu zukommen lassen.
Grüße
Zitieren
#6
Hallo Reiner,

wenn die App startet und Du die sehr einfache Benutzeroberfläche von oben siehst, dann funktioniert die App. Ein neu kompilieren würde da auch keine andere apk-Datei erzeugen als die schon vorhandene.

Mal eine Frage, liegt dein Download-Ordner im interen Speicher im Telefon oder auf einer SD-Karte?


Gruß
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#7
Hallo Bernhard
Download Ordner liegt im Telefon.

Grüße
Zitieren
#8
Hallo Reiner,

ich habe nochmal den Quellcode von oben geprüft, ganz besonders diesen Teil:  Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

Wenn ich die Systemdokumentation dazu durchlese, komme ich zum Eindruck das diese Anweisung sich nur auf Download-Ordner bezieht, die von Android auf einen externen Speicher (ich verstehe darunter SD-Karte im Telefon) und nicht auf den Download-Ordner im internen, eingebauten Flash-Speicher des Telefons abgelegt wurden.

Bitte lege mal eine SD-Karte in das Telefon ein und lasse darauf von Android alle Downloads umleiten (Speichermanager!). Spiele dann die playlist.m3u in diesen Ordner auf der SD-Karte ein und starte die App neu (vorher laufende Instanzen beenden!).
Man kann an der Stelle im Quellcode natürlich auch explizit einen internen Speicherort angeben, siehe auskommentierten Code von oben, oder sogar nach mehreren Speicherorten intern wie extern suchen lassen! Danach muss man aber mit dem Android-Studio eine neue APK-erzeugen und aufspielen, damit die Änderungen dauerhaft wirksam werden.
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#9
Hallo Bernhard
alles versucht es funktioniert nicht.
Ich gebe auf.
Danke für Deine Bemühungen
Grüße
Zitieren
#10
Hallo Bernhard
noch eine Überlegung kann man nicht die sender in der App vorinstallieren wie die Sender die starten wenn nichts gefunden wird?

wie komme ich in die App und kann das ändern?
Ein Versuch ist es noch wert.

Grüße
Zitieren
#11
Hallo Reiner,

ich habe die APK auch nochmal auf ein Android-Smartphone aufgespielt, keine Probleme beim finden und einlesen der Senderliste von oben. Irgendwo musst Du also den falschen Speicherort gewählt haben oder einen Fehler beim Transfer der Senderliste auf das Smartphone haben.

Zu deiner Frage ob man die Sender vorinstallieren kann. Ja das geht natürlich. Im allerersten Post ist dazu schon der Hinweis gegeben. An folgender Stelle im Programmcode

if (!filePlaylist.canRead() || !filePlaylist.isFile()) {
Log.e("Senderliste:" , "keine gefunden, nutze Defaultliste!");
streamURLs.add("http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio2_mf_p");
streamURLs.add("http://st02.dlf.de/dlf/02/128/mp3/stream.mp3");
streamURLs.add("http://str0.creacast.com/hitradio_ohr_thema1");
streamURLs.add("http://stream.srg-ssr.ch/m/drs4news/aacp_96");
streamURLs.add("http://stream1.evasionfm.com/Chante_France");
}

ist der Fall definiert, der passieren soll, wenn keine playlist.m3u gefunden wird. Über die add-Methode wird der Liste streamURLs, welche ja die Senderliste im Programm repräsentiert, eine weitere Internetradiostation hinzugefügt. In dem gezeigten Fall ist das die BBC2, der DLF, Schwarzwaldradio, SRF4 und Chante France. Die Zeilen kannst Du löschen wenn Du diese Default-Sender nicht haben willst und durch deine eigenen Sender ersetzen.

Gruß Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#12
Hallo Bernhard
danke für Deine Nachricht muss mal alles nochmal genau durchgehen.
Jetzt ist nur die Frage wie kann ich in der App die Liste ändern.Die add-Methode kenne ich nicht muss ich mal googeln .
Gibt es ein Programm um die apk Datei zu öffnen die Senderliste zu tauschen und wieder einzufügen?
Kannst Du mir da mal erklären was ich machen muss.

Grüße
Zitieren
#13
Werbefrei und läuft auch auf altersschwacher Hardware: Radionator

- Mehr als 10.000 Sender aus aller Welt
- Favoriten
- Equalizer
- Wecker
- Sleep Timer
- Steuerung über Bluetooth-Kopfhörer
- Widgets für den Startbildschirm
- Funktioniert mit Android Auto
- Eigene Sender hinzufügen
- Keine Werbung in der App

Wollte dem Entwickler, den ich aus einem anderen Forum kenne einen kleinen Betrag spenden, aber er lehnte dankend ab.

Radionator im Google Playstore
Zitieren
#14
Hallo
das ist eine sehr gute App danke für den Tip.
Grüße
Zitieren
#15
(24.05.2021, 10:26)reinersbg schrieb: Hallo Bernhard
danke für Deine Nachricht muss mal alles nochmal genau durchgehen.
Jetzt ist nur die Frage wie kann ich in der App die Liste ändern.Die add-Methode kenne ich nicht muss ich mal googeln .
Gibt es ein Programm um die apk Datei zu öffnen die Senderliste zu tauschen und wieder einzufügen?
Kannst Du mir da mal erklären was ich machen muss.

Hallo Reiner, 


im Prinzip steht alles zu deiner Frage im 1. Post dieses Threads, auch wie das mit der add-Methode funktioniert.
Ziel des Threads war es (bitte nochmal genau 1. Post lesen) mal aufzuzeigen, wie man eine kleine, sparsame und werbefreie Internetradio-App für Android selbst mit dem Android Studio zusammen programmieren kann! Jeden funktionalen Teil des Quellcodes habe ich, also auch wie das mit der Senderliste und der add-Methode läuft, oben im Post kommentiert. Am Ende wurde der gesamte Quelltext dieser Demoanwendung und die darauf erstellte APK hinzugefügt. 

Anhand dieser Anleitung und des Democodes soll(te) jeder auf Basis eines alten Smartphone-Boards ein Radio selbst, nach eigenen Ideen und mit eigener gestalteter Nutzeroberfläche zusammenbauen können. 

Was ist nicht die Absicht dieses Threads? Den Democode und die APK als fertige Internetradioanwendung aufzufassen und so in eigene Projekte 1:1 zu verwursten! Das wollen wir ja auch nicht mit dem iRadio hier: BM45/iRadio: Ein Softwarebaukasten für den Aufbau/Umbau von Radios auf Raspberry Pi Basis (github.com)

Sowohl im iRadio, als auch hier soll ein grober Rahmen skizziert werden und der Nutzer für eigene Entwicklungen mit abgelegter Smartphonehardware motiviert werden. Das das geht, zeigen ja auch hier im Forum unzählige Projekte. ABER: Diese Projekte sind nur möglich gewesen, weil die Bastler sich hingesetzt und wenigstens die Grundfähigkeiten hinsichtlich der Programmierung angesehen haben. 

Im Prinzip bedeutet das für Dich, wenn Du diesen hier beschriebenen Weg gehen willst: 

- Android Studio downloaden und eine Entwicklermaschine aufsetzen
- Demo-Quellcode laden und/oder mit den Demo-Quellcode als Rahmen eine eigene Internetradio-App erstellen
- App kompilieren und als APK ausspielen
- App auf dem Handy oder im Emulator testen
- Verbesserungen oder Änderungswünsche? -> zurück zu Schritt weiter oben
- Wenn mit App zufrieden und Nutzeroberfläche oder sogar Skalensimulation entspricht den Wünschen, dann Smartphone(platine) in ein passendes Radiogehäuse bauen

Ziel: aha-Effekt und etwas gelernt zu haben

Deine Frage: "Jetzt ist nur die Frage wie kann ich in der App die Liste ändern..." zeigt mir das Du noch nicht verstanden oder erkannt hast, was im 1. Post eigentlich gemacht wird und das wir hier direkt im Quellcode der App sind. Ich vermute mal Du kannst auch noch nicht aus dem Quellcode heraus kompilieren und eine APK erstellen? Wenn dem so ist, dann musst Du erstmal noch ein paar Schritte weiter vorne anfangen. Also ein Entwicklungssystem für Apps aufbauen. Ohne das geht nämlich hier im Thread gar nichts in Richtung Internetradio nach eigenen Vorstellungen oder Sender in Default-Liste der App ändern, denn wie schon oben gesagt und gezeigt sind diese im Quelltext abgelegt.

Gruß Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
#16
Hallo Bernhard
ich werde mir das mal genau anschauen und wenn ich nicht weiter komme Dich um Rat fragen.Danke erstmal für Deine Hilfe.
Grüße
Zitieren


Möglicherweise verwandte Themen…
Thema Verfasser Antworten Ansichten Letzter Beitrag
  iRadioAndroid - iRadio Portierung für Android Geräte OttoBerger 154 9.104 23.03.2024, 13:45
Letzter Beitrag: Uli
  Neue Erfahrungen mit FL2K und AM Stereo/Mono Semir 2 257 20.03.2024, 22:40
Letzter Beitrag: Semir
  Huth E72 Internetradio mit ESP32 saarfranzose 10 1.785 12.05.2023, 14:49
Letzter Beitrag: saarfranzose
  Ein Internetradio Bausatz von PIMORONI Bernhard45 200 81.579 06.06.2022, 19:17
Letzter Beitrag: Reparateur
  Ein minimales Internetradio für alte und neue Raspberrys Bernhard45 189 105.676 12.04.2022, 16:00
Letzter Beitrag: navi

Gehe zu: