Merge branch 'backgroundPlayback'

This commit is contained in:
Christian Schabesberger 2015-12-20 18:16:28 +01:00
commit 72289ced39
13 changed files with 345 additions and 49 deletions

View file

@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.schabi.newpipe" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name= "android.permission.INTERNET" />
<uses-permission android:name= "android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
@ -74,6 +74,12 @@
android:parentActivityName=".VideoItemDetailActivity"
tools:ignore="UnusedAttribute">
</activity>
<!--TODO: make label a translatable string -->
<service
android:name=".BackgroundPlayer"
android:label="NewPipe Background Player"
android:exported="false" >
</service>
<activity
android:name=".SettingsActivity"
android:label="@string/title_activity_settings" >

View file

@ -120,6 +120,9 @@ class ActionBarHandler {
}
}
}
else {
Log.e(TAG, "FAILED to set audioStream value!");
}
}
private void selectFormatItem(int i) {
@ -136,7 +139,7 @@ class ActionBarHandler {
MenuItem castItem = menu.findItem(R.id.action_play_with_kodi);
castItem.setVisible(defaultPreferences
.getBoolean(activity.getString(R.string.showPlayWidthKodiPreference), false));
.getBoolean(activity.getString(R.string.showPlayWithKodiPreference), false));
}
public boolean onItemSelected(MenuItem item) {
@ -184,7 +187,7 @@ class ActionBarHandler {
// ----------- THE MAGIC MOMENT ---------------
if(!videoTitle.isEmpty()) {
if (PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.useExternalPlayer), false)) {
.getBoolean(activity.getString(R.string.useExternalVideoPlayer), false)) {
// External Player
Intent intent = new Intent();
@ -293,37 +296,56 @@ class ActionBarHandler {
}
}
private void playAudio() {
Intent intent = new Intent();
try {
public void playAudio() {
boolean externalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.useExternalAudioPlayer), false);
Intent intent;
if (!externalAudioPlayer)//internal (background) music player: explicit intent
{
intent = new Intent(activity, BackgroundPlayer.class);
intent.setAction(Intent.ACTION_VIEW);
Log.i(TAG, "audioStream is null:" + (audioStream == null));
Log.i(TAG, "audioStream.url is null:"+(audioStream.url==null));
intent.setDataAndType(Uri.parse(audioStream.url),
MediaFormat.getMimeById(audioStream.format));
intent.putExtra(Intent.EXTRA_TITLE, videoTitle);
intent.putExtra("title", videoTitle);
activity.startActivity(intent); // HERE !!!
} catch (Exception e) {
e.printStackTrace();
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.noPlayerFound)
.setPositiveButton(R.string.installStreamPlayer, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(activity.getString(R.string.fdroidVLCurl)));
activity.startActivity(intent);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "You unlocked a secret unicorn.");
}
});
builder.create().show();
Log.e(TAG, "Either no Streaming player for audio was installed, or something important crashed:");
e.printStackTrace();
activity.startService(intent);
} else {
intent = new Intent();
try {
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(audioStream.url),
MediaFormat.getMimeById(audioStream.format));
intent.putExtra(Intent.EXTRA_TITLE, videoTitle);
intent.putExtra("title", videoTitle);
activity.startActivity(intent); // HERE !!!
} catch (Exception e) {
e.printStackTrace();
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.noPlayerFound)
.setPositiveButton(R.string.installStreamPlayer, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(activity.getString(R.string.fdroidVLCurl)));
activity.startActivity(intent);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "You unlocked a secret unicorn.");
}
});
builder.create().show();
Log.e(TAG, "Either no Streaming player for audio was installed, or something important crashed:");
e.printStackTrace();
}
}
}
}

View file

@ -0,0 +1,255 @@
package org.schabi.newpipe;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.wifi.WifiManager;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.v7.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
import java.io.IOException;
/**
* Created by Adam Howard on 08/11/15.
* Copyright (c) Adam Howard <achdisposable1@gmail.com> 2015
*
* BackgroundPlayer.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe 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 NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
/**Plays the audio stream of videos in the background.*/
public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPreparedListener*/ {
private static final String TAG = BackgroundPlayer.class.toString();
private static final String ACTION_STOP = TAG+".STOP";
private static final String ACTION_PLAYPAUSE = TAG+".PLAYPAUSE";
public BackgroundPlayer() {
super();
}
@Override
public void onCreate() {
/*PendingIntent pi = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);*/
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "Playing in background", Toast.LENGTH_SHORT).show();//todo:translation string
String source = intent.getDataString();
//Log.i(TAG, "backgroundPLayer source:"+source);
String videoTitle = intent.getStringExtra("title");
//do nearly everything in a separate thread
PlayerThread player = new PlayerThread(source, videoTitle, this);
player.start();
// If we get killed after returning here, don't restart
return START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// We don't provide binding (yet?), so return null
return null;
}
@Override
public void onDestroy() {
//Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
private class PlayerThread extends Thread {
MediaPlayer mediaPlayer;
private String source;
private String title;
private int noteID = TAG.hashCode();
private BackgroundPlayer owner;
private NotificationManager noteMgr;
private NotificationCompat.Builder noteBuilder;
private WifiManager.WifiLock wifiLock;
public PlayerThread(String src, String title, BackgroundPlayer owner) {
this.source = src;
this.title = title;
this.owner = owner;
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
@Override
public void run() {
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);//cpu lock
try {
mediaPlayer.setDataSource(source);
mediaPlayer.prepare(); //We are already in a separate worker thread,
//so calling the blocking prepare() method should be ok
//alternatively:
//mediaPlayer.setOnPreparedListener(this);
//mediaPlayer.prepareAsync(); //prepare async to not block main thread
} catch (IOException ioe) {
ioe.printStackTrace();
Log.e(TAG, "video source:" + source);
Log.e(TAG, "video title:" + title);
//can't do anything useful without a file to play; exit early
return;
}
WifiManager wifiMgr = ((WifiManager)getSystemService(Context.WIFI_SERVICE));
wifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
mediaPlayer.setOnCompletionListener(new EndListener(wifiLock));//listen for end of video
//get audio focus
/*
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// could not get audio focus.
}*/
wifiLock.acquire();
mediaPlayer.start();
IntentFilter filter = new IntentFilter();
filter.setPriority(Integer.MAX_VALUE);
filter.addAction(ACTION_PLAYPAUSE);
filter.addAction(ACTION_STOP);
registerReceiver(broadcastReceiver, filter);
PendingIntent playPI = PendingIntent.getBroadcast(owner, noteID, new Intent(ACTION_PLAYPAUSE), PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Action playButton = new NotificationCompat.Action.Builder
(R.drawable.ic_play_arrow_white_48dp, "Play", playPI).build();
NotificationCompat.Action pauseButton = new NotificationCompat.Action.Builder
(R.drawable.ic_play_arrow_white_48dp, "Pause", playPI).build();
PendingIntent stopPI = PendingIntent.getBroadcast(owner, noteID,
new Intent(ACTION_STOP), PendingIntent.FLAG_UPDATE_CURRENT);
//todo: make it so that tapping the notification brings you back to the Video's DetailActivity
//using setContentIntent
noteBuilder = new NotificationCompat.Builder(owner);
noteBuilder
.setPriority(Notification.PRIORITY_LOW)
.setCategory(Notification.CATEGORY_TRANSPORT)
.setContentTitle(title)
.setContentText("NewPipe is playing in the background")//todo: translation string
//.setAutoCancel(!mediaPlayer.isPlaying())
.setOngoing(true)
.setDeleteIntent(stopPI)
//.setProgress(vidLength, 0, false) //doesn't fit with Notification.MediaStyle
.setSmallIcon(R.mipmap.ic_launcher)
.setTicker(title + " - NewPipe")
.addAction(playButton);
/* .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setLargeIcon(cover)*/
noteBuilder.setStyle(new NotificationCompat.MediaStyle()
//.setMediaSession(mMediaSession.getSessionToken())
.setShowActionsInCompactView(new int[] {0})
.setShowCancelButton(true)
.setCancelButtonIntent(stopPI)
);
startForeground(noteID, noteBuilder.build());
//currently decommissioned progressbar looping update code - works, but doesn't fit inside
//Notification.MediaStyle Notification layout.
noteMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
/*
//update every 2s or 4 times in the video, whichever is shorter
int sleepTime = Math.min(2000, (int)((double)vidLength/4));
while(mediaPlayer.isPlaying()) {
noteBuilder.setProgress(vidLength, mediaPlayer.getCurrentPosition(), false);
noteMgr.notify(noteID, noteBuilder.build());
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
Log.d(TAG, "sleep failure");
}
}*/
}
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.i(TAG, "received broadcast action:"+action);
if(action.equals(ACTION_PLAYPAUSE)) {
if(mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
else {
//reacquire CPU lock after releasing it on pause
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
mediaPlayer.start();
}
}
else if(action.equals(ACTION_STOP)) {
mediaPlayer.stop();//this auto-releases CPU lock
afterPlayCleanup();
}
}
};
private void afterPlayCleanup() {
//noteBuilder.setProgress(0, 0, false);//remove progress bar
noteMgr.cancel(noteID);//remove notification
unregisterReceiver(broadcastReceiver);
mediaPlayer.release();//release mediaPlayer's system resources
wifiLock.release();//release wifilock
stopForeground(true);//remove foreground status of service; make us killable
stopSelf();
}
private class EndListener implements MediaPlayer.OnCompletionListener {
private WifiManager.WifiLock wl;
public EndListener(WifiManager.WifiLock wifiLock) {
this.wl = wifiLock;
}
@Override
public void onCompletion(MediaPlayer mp) {
afterPlayCleanup();
}
}
}
/*
private class ListenerThread extends Thread implements AudioManager.OnAudioFocusChangeListener {
@Override
public void onAudioFocusChange(int focusChange) {
}
}*/
}

View file

@ -30,6 +30,6 @@ public interface StreamingService {
/**When a VIEW_ACTION is caught this function will test if the url delivered within the calling
Intent was meant to be watched with this Service.
Return false if this service shall not allow to be callean through ACTIONs.*/
Return false if this service shall not allow to be called through ACTIONs.*/
boolean acceptUrl(String videoUrl);
}

View file

@ -371,6 +371,16 @@ public class YoutubeVideoExtractor extends VideoExtractor {
//todo: replace this with a call to getVideoId, if possible
videoInfo.id = matchGroup1("v=([0-9a-zA-Z_-]{11})", pageUrl);
if(videoInfo.audioStreams == null
|| videoInfo.audioStreams.length == 0) {
Log.e(TAG, "uninitialised audio streams!");
}
if(videoInfo.videoStreams == null
|| videoInfo.videoStreams.length == 0) {
Log.e(TAG, "uninitialised video streams!");
}
videoInfo.age_limit = 0;
//average rating
@ -445,13 +455,14 @@ public class YoutubeVideoExtractor extends VideoExtractor {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new StringReader(dashDoc));
int eventType = parser.getEventType();
String tagName = "";
String currentMimeType = "";
int currentBandwidth = -1;
int currentSamplingRate = -1;
boolean currentTagIsBaseUrl = false;
while(eventType != XmlPullParser.END_DOCUMENT) {
for(int eventType = parser.getEventType();
eventType != XmlPullParser.END_DOCUMENT;
eventType = parser.next() ) {
switch(eventType) {
case XmlPullParser.START_TAG:
tagName = parser.getName();
@ -465,8 +476,8 @@ public class YoutubeVideoExtractor extends VideoExtractor {
} else if(tagName.equals("BaseURL")) {
currentTagIsBaseUrl = true;
}
break;
case XmlPullParser.TEXT:
if(currentTagIsBaseUrl &&
(currentMimeType.contains("audio"))) {
@ -479,16 +490,14 @@ public class YoutubeVideoExtractor extends VideoExtractor {
audioStreams.add(new VideoInfo.AudioStream(parser.getText(),
format, currentBandwidth, currentSamplingRate));
}
//missing break here?
case XmlPullParser.END_TAG:
if(tagName.equals("AdaptationSet")) {
currentMimeType = "";
} else if(tagName.equals("BaseURL")) {
currentTagIsBaseUrl = false;
}
break;
default:
}//no break needed here
}
eventType = parser.next();
}
} catch(Exception e) {
e.printStackTrace();
@ -582,10 +591,7 @@ public class YoutubeVideoExtractor extends VideoExtractor {
e.printStackTrace();
}
Context.exit();
if(result != null)
return result.toString();
else
return "";
return (result == null ? "" : result.toString());
}
private String cleanUrl(String complexUrl) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

View file

@ -6,9 +6,10 @@
<string name="settingsCategoryEtc">settings_category_etc</string>
<!-- Key values -->
<string name="downloadPathPreference">download_path_preference</string>
<string name="useExternalPlayer">use_external_player</string>
<string name="useExternalVideoPlayer">use_external_video_player</string>
<string name="useExternalAudioPlayer">use_external_audio_player</string>
<string name="autoPlayThroughIntent">autoplay_through_intent</string>
<string name="defaultResolutionPreference">default_resulution_preference</string>
<string name="defaultResolutionPreference">default_resolution_preference</string>
<string-array name="resolutionList">
<item>720p</item>
<item>360p</item>
@ -16,7 +17,7 @@
<item>144p</item>
</string-array>
<string name="defaultResolutionListItem">360p</string>
<string name="showPlayWidthKodiPreference">show_play_with_kodi_preference</string>
<string name="showPlayWithKodiPreference">show_play_with_kodi_preference</string>
<string name="defaultAudioFormatPreference">default_audio_format</string>
<string-array name="audioFormatDescriptionList">
<item>@string/webMAudioDescription</item>

View file

@ -19,7 +19,8 @@
<string name="chooseBrowser">Choose browser:</string>
<string name="screenRotation">rotation</string>
<string name="title_activity_settings">Settings</string>
<string name="useExternalPlayerTitle">Use external player</string>
<string name="useExternalVideoPlayerTitle">Use external video player</string>
<string name="useExternalAudioPlayerTitle">Use external audio player</string>
<string name="downloadLocation">Download location</string>
<string name="downloadLocationSummary">Path to store downloaded videos in.</string>
<string name="downloadLocationDialogTitle">Enter download path</string>

View file

@ -8,8 +8,13 @@
android:title="@string/settingsCategoryVideoAudioTitle">
<CheckBoxPreference
android:key="@string/useExternalPlayer"
android:title="@string/useExternalPlayerTitle"
android:key="@string/useExternalVideoPlayer"
android:title="@string/useExternalVideoPlayerTitle"
android:defaultValue="false"/>
<CheckBoxPreference
android:key="@string/useExternalAudioPlayer"
android:title="@string/useExternalAudioPlayerTitle"
android:defaultValue="false"/>
<ListPreference
@ -32,7 +37,7 @@
android:title="@string/settingsCategoryVideoInfoTittle">
<CheckBoxPreference
android:key="@string/showPlayWidthKodiPreference"
android:key="@string/showPlayWithKodiPreference"
android:title="@string/showPlayWithKodiTitle"
android:summary="@string/showPlayWithKodiSummary"
android:defaultValue="false" />