Merge branch 'master' into fixBlackBgLostOnOrientationChange

This commit is contained in:
David Luhmer 2018-11-04 21:08:08 +01:00 committed by GitHub
commit 694b1d09fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 601 additions and 249 deletions

View file

@ -83,13 +83,6 @@
<activity android:name="DirectoryChooserActivity" />
<receiver android:name=".events.podcast.broadcastreceiver.PodcastNotificationToggle">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
<!--
**********************************************************************

View file

@ -22,7 +22,7 @@ public class YoutubePlaybackService extends PlaybackService {
Context context;
public YoutubePlaybackService(Context context, PodcastStatusListener podcastStatusListener, MediaItem mediaItem) {
super(context, podcastStatusListener, mediaItem);
super(podcastStatusListener, mediaItem);
this.context = context;
setStatus(Status.PREPARING);
}

View file

@ -99,12 +99,6 @@
</activity>
<activity android:name=".DirectoryChooserActivity" />
<receiver android:name=".events.podcast.broadcastreceiver.PodcastNotificationToggle">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.intent.action.HEADSET_PLUG" />
</intent-filter>
</receiver>
<!--
**********************************************************************
@ -199,13 +193,37 @@
android:name=".widget.WidgetService"
android:exported="false"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<!--
**********************************************************************
* Podcast
**********************************************************************
-->
<service
android:name=".services.PodcastPlaybackService"
android:enabled="true"
android:exported="true" /> <!-- android:process=":podcastPlaybackService" -->
android:exported="true" >
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service> <!-- android:process=":podcastPlaybackService" -->
<receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
<service
android:name=".services.PodcastDownloadService"
android:exported="false" />
<!--
**********************************************************************
* Chrome-Custom Tabs
**********************************************************************
-->
<service
android:name=".chrometabs.KeepAliveService"
android:exported="true"

View file

@ -444,7 +444,7 @@ public class NewsDetailActivity extends PodcastFragmentActivity {
break;
case R.id.action_tts:
TTSItem ttsItem = new TTSItem(rssItem.getId(), rssItem.getTitle(), rssItem.getTitle() + "\n\n " + Html.fromHtml(rssItem.getBody()).toString(), rssItem.getFeed().getFaviconUrl());
TTSItem ttsItem = new TTSItem(rssItem.getId(), rssItem.getAuthor(), rssItem.getTitle(), rssItem.getTitle() + "\n\n " + Html.fromHtml(rssItem.getBody()).toString(), rssItem.getFeed().getFaviconUrl());
openMediaItem(ttsItem);
break;

View file

@ -36,6 +36,7 @@ import org.greenrobot.eventbus.Subscribe;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Locale;
import butterknife.BindView;
import butterknife.ButterKnife;
@ -168,7 +169,7 @@ public class PodcastFragment extends Fragment {
}
if(lastPodcastRssItemId != podcast.getRssItemId() && imgFavIcon != null) {
if(loadPodcastFavIcon()) { //Returns false if PodcastItem is not found (e.g. Service is not connected to Activity yet)
if(loadPodcastFavIcon()) {
lastPodcastRssItemId = podcast.getRssItemId();
}
}
@ -177,15 +178,15 @@ public class PodcastFragment extends Fragment {
int minutes = (int)(podcast.getCurrent() % (1000*60*60)) / (1000*60);
int seconds = (int) ((podcast.getCurrent() % (1000*60*60)) % (1000*60) / 1000);
minutes += hours * 60;
tvFrom.setText(String.format("%02d:%02d", minutes, seconds));
tvFromSlider.setText(String.format("%02d:%02d", minutes, seconds));
tvFrom.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
tvFromSlider.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
hours = (int)( podcast.getMax() / (1000*60*60));
minutes = (int)(podcast.getMax() % (1000*60*60)) / (1000*60);
seconds = (int) ((podcast.getMax() % (1000*60*60)) % (1000*60) / 1000);
minutes += hours * 60;
tvTo.setText(String.format("%02d:%02d", minutes, seconds));
tvToSlider.setText(String.format("%02d:%02d", minutes, seconds));
tvTo.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
tvToSlider.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
tvTitle.setText(podcast.getTitle());
tvTitleSlider.setText(podcast.getTitle());
@ -210,9 +211,14 @@ public class PodcastFragment extends Fragment {
}
private boolean loadPodcastFavIcon() {
MediaItem podcastItem = ((PodcastFragmentActivity) getActivity()).getCurrentPlayingPodcast();
if(podcastItem != null) {
String favIconUrl = podcastItem.favIcon;
return ((PodcastFragmentActivity) getActivity()).getCurrentPlayingPodcast(
new PodcastFragmentActivity.OnCurrentPlayingPodcastCallback() {
@Override
public void currentPlayingPodcastReceived(MediaItem mediaItem) {
Log.d(TAG, "currentPlayingPodcastReceived() called with: mediaItem = [" + mediaItem + "]");
if(mediaItem != null) {
String favIconUrl = mediaItem.favIcon;
Log.d(TAG, "currentPlayingPodcastReceived: " + favIconUrl);
DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().
showImageOnLoading(R.drawable.default_feed_icon_light).
showImageForEmptyUri(R.drawable.default_feed_icon_light).
@ -220,7 +226,8 @@ public class PodcastFragment extends Fragment {
build();
ImageLoader.getInstance().displayImage(favIconUrl, imgFavIcon, displayImageOptions);
}
return podcastItem != null;
}
});
}
@ -258,11 +265,12 @@ public class PodcastFragment extends Fragment {
boolean hasTitleInCache = false;
@OnClick(R.id.fl_playPausePodcastWrapper) void playPause() {
if(!hasTitleInCache)
if(!hasTitleInCache) {
Toast.makeText(getActivity(), "Please select a title first", Toast.LENGTH_SHORT).show();
else
} else {
eventBus.post(new TogglePlayerStateEvent());
}
}
@OnClick(R.id.btn_playPausePodcastSlider) void playPauseSlider() {
playPause();
@ -437,9 +445,14 @@ public class PodcastFragment extends Fragment {
});
if(getActivity() instanceof PodcastFragmentActivity) {
float playbackSpeed = ((PodcastFragmentActivity) getActivity()).getCurrentPlaybackSpeed();
((PodcastFragmentActivity) getActivity()).getCurrentPlaybackSpeed(new PodcastFragmentActivity.OnPlaybackSpeedCallback() {
@Override
public void currentPlaybackReceived(float playbackSpeed) {
int position = Arrays.binarySearch(PodcastPlaybackService.PLAYBACK_SPEEDS, playbackSpeed);
numberPicker.setValue(position);
}
});
} else {
numberPicker.setValue(3);
}

View file

@ -7,13 +7,17 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.view.GravityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
@ -63,24 +67,27 @@ import de.luhmer.owncloudnewsreader.view.ZoomableRelativeLayout;
import de.luhmer.owncloudnewsreader.widget.WidgetProvider;
import static de.luhmer.owncloudnewsreader.Constants.MIN_NEXTCLOUD_FILES_APP_VERSION_CODE;
import static de.luhmer.owncloudnewsreader.services.PodcastPlaybackService.CURRENT_PODCAST_ITEM_MEDIA_ITEM;
import static de.luhmer.owncloudnewsreader.services.PodcastPlaybackService.PLAYBACK_SPEED_FLOAT;
public class PodcastFragmentActivity extends AppCompatActivity implements IPlayPausePodcastClicked {
private static final String TAG = PodcastFragmentActivity.class.getCanonicalName();
@Inject SharedPreferences mPrefs;
@Inject ApiProvider mApi;
@Inject public MemorizingTrustManager mMTM;
@Inject protected SharedPreferences mPrefs;
@Inject protected ApiProvider mApi;
@Inject protected MemorizingTrustManager mMTM;
private PodcastPlaybackService mPodcastPlaybackService;
private boolean mBound = false;
private MediaBrowserCompat mMediaBrowser;
private EventBus eventBus;
private PodcastFragment mPodcastFragment;
private int appHeight;
private int appWidth;
@BindView(R.id.videoPodcastSurfaceWrapper) ZoomableRelativeLayout rlVideoPodcastSurfaceWrapper;
@BindView(R.id.sliding_layout) PodcastSlidingUpPanelLayout sliding_layout;
@BindView(R.id.videoPodcastSurfaceWrapper)
protected ZoomableRelativeLayout rlVideoPodcastSurfaceWrapper;
@BindView(R.id.sliding_layout)
protected PodcastSlidingUpPanelLayout sliding_layout;
//YouTubePlayerFragment youtubeplayerfragment;
private boolean currentlyPlaying = false;
@ -103,7 +110,7 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
((NewsReaderApplication) getApplication()).getAppComponent().injectActivity(this);
if(mApi.getAPI() instanceof API_SSO) {
if (mApi.getAPI() instanceof API_SSO) {
VersionCheckHelper.verifyMinVersion(this, MIN_NEXTCLOUD_FILES_APP_VERSION_CODE);
}
@ -149,10 +156,16 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
updatePodcastView();
if(isMyServiceRunning(PodcastPlaybackService.class, this)) {
/*
if (isMyServiceRunning(PodcastPlaybackService.class, this)) {
Intent intent = new Intent(this, PodcastPlaybackService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
*/
mMediaBrowser = new MediaBrowserCompat(this,
new ComponentName(this, PodcastPlaybackService.class),
mConnectionCallbacks,
null); // optional Bundle
super.onPostCreate(savedInstanceState);
}
@ -163,20 +176,12 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
super.onStop();
unbindPodcastService();
}
private void unbindPodcastService() {
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
mMediaBrowser.disconnect();
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
if(hasWindowFocus) {
if (hasWindowFocus) {
int currentOrientation = getResources().getConfiguration().orientation;
if (currentOrientation != lastOrientation) {
sliding_layout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
@ -188,13 +193,13 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
}
int lastOrientation = -1;
@Override
protected void onResume() {
eventBus.register(this);
if(mPodcastPlaybackService != null && !mPodcastPlaybackService.isActive()) {
if (mMediaBrowser != null && !mMediaBrowser.isConnected()) {
sliding_layout.setPanelHeight(0);
sliding_layout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
@ -221,12 +226,12 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
WidgetProvider.UpdateWidget(this);
if(NextcloudNotificationManager.isUnreadRssCountNotificationVisible(this)) {
if (NextcloudNotificationManager.isUnreadRssCountNotificationVisible(this)) {
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(this);
int count = Integer.parseInt(dbConn.getUnreadItemsCountForSpecificFolder(SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_UNREAD_ITEMS));
NextcloudNotificationManager.showUnreadRssItemsNotification(this, count);
if(count == 0) {
if (count == 0) {
NextcloudNotificationManager.removeRssItemsNotification(this);
}
}
@ -246,30 +251,71 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
return false;
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
private final MediaBrowserCompat.ConnectionCallback mConnectionCallbacks =
new MediaBrowserCompat.ConnectionCallback() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
PodcastPlaybackService.LocalBinder binder = (PodcastPlaybackService.LocalBinder) service;
mPodcastPlaybackService = binder.getService();
mBound = true;
public void onConnected() {
Log.d(TAG, "onConnected() called");
// Get the token for the MediaSession
MediaSessionCompat.Token token = mMediaBrowser.getSessionToken();
try {
// Create a MediaControllerCompat
MediaControllerCompat mediaController = new MediaControllerCompat(PodcastFragmentActivity.this, token);
// Save the controller
MediaControllerCompat.setMediaController(PodcastFragmentActivity.this, mediaController);
// Finish building the UI
//buildTransportControls();
} catch (RemoteException e) {
Log.e(TAG, "Connecting to podcast service failed!", e);
}
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
public void onConnectionSuspended() {
Log.d(TAG, "onConnectionSuspended() called");
// The Service has crashed. Disable transport controls until it automatically reconnects
}
@Override
public void onConnectionFailed() {
Log.e(TAG, "onConnectionFailed() called");
// The Service has refused our connection
}
};
public MediaItem getCurrentPlayingPodcast() {
if(mPodcastPlaybackService != null)
return mPodcastPlaybackService.getCurrentlyPlayingPodcast();
return null;
/*
private void buildTransportControls() {
// Grab the view for the play/pause button
int pbState = MediaControllerCompat.getMediaController(PodcastFragmentActivity.this).getPlaybackState().getState();
if (pbState == PlaybackStateCompat.STATE_PLAYING) {
MediaControllerCompat.getMediaController(PodcastFragmentActivity.this).getTransportControls().pause();
} else {
MediaControllerCompat.getMediaController(PodcastFragmentActivity.this).getTransportControls().play();
}
MediaControllerCompat mediaController = MediaControllerCompat.getMediaController(PodcastFragmentActivity.this);
// Display the initial state
MediaMetadataCompat metadata = mediaController.getMetadata();
PlaybackStateCompat pbState = mediaController.getPlaybackState();
// Register a Callback to stay in sync
mediaController.registerCallback(controllerCallback);
}
MediaControllerCompat.Callback controllerCallback =
new MediaControllerCompat.Callback() {
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {}
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {}
};
*/
public PodcastSlidingUpPanelLayout getSlidingLayout() {
return sliding_layout;
@ -581,7 +627,12 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
Intent intent = new Intent(this, PodcastPlaybackService.class);
intent.putExtra(PodcastPlaybackService.MEDIA_ITEM, mediaItem);
startService(intent);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
if(!mMediaBrowser.isConnected()) {
mMediaBrowser.connect();
}
//bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
@ -623,11 +674,44 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
@Override
public void pausePodcast() {
mPodcastPlaybackService.pause();
MediaControllerCompat.getMediaController(PodcastFragmentActivity.this).getTransportControls().pause();
}
public float getCurrentPlaybackSpeed() {
return mPodcastPlaybackService.getPlaybackSpeed();
public void getCurrentPlaybackSpeed(final OnPlaybackSpeedCallback callback) {
MediaControllerCompat.getMediaController(PodcastFragmentActivity.this)
.sendCommand(PLAYBACK_SPEED_FLOAT,
null,
new ResultReceiver(new Handler()) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
callback.currentPlaybackReceived(resultData.getFloat(PLAYBACK_SPEED_FLOAT));
}
});
}
public boolean getCurrentPlayingPodcast(final OnCurrentPlayingPodcastCallback callback) {
if(mMediaBrowser != null && mMediaBrowser.isConnected()) {
MediaControllerCompat.getMediaController(PodcastFragmentActivity.this)
.sendCommand(CURRENT_PODCAST_ITEM_MEDIA_ITEM,
null,
new ResultReceiver(new Handler()) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
callback.currentPlayingPodcastReceived((MediaItem) resultData.getSerializable(CURRENT_PODCAST_ITEM_MEDIA_ITEM));
}
});
return true;
} else {
return false;
}
}
public interface OnPlaybackSpeedCallback {
void currentPlaybackReceived(float playbackSpeed);
}
public interface OnCurrentPlayingPodcastCallback {
void currentPlayingPodcastReceived(MediaItem mediaItem);
}
}

View file

@ -6,6 +6,7 @@ public class UpdatePodcastStatusEvent {
private long current;
private long max;
private String author;
private String title;
private PlaybackService.Status status;
private PlaybackService.VideoType videoType;
@ -16,6 +17,10 @@ public class UpdatePodcastStatusEvent {
return rssItemId;
}
public String getAuthor() {
return author;
}
public String getTitle() {
return title;
}
@ -42,10 +47,11 @@ public class UpdatePodcastStatusEvent {
public boolean isVideoFile() { return !(videoType == PlaybackService.VideoType.None); }
public UpdatePodcastStatusEvent(long current, long max, PlaybackService.Status status, String title, PlaybackService.VideoType videoType, long rssItemId, float speed) {
public UpdatePodcastStatusEvent(long current, long max, PlaybackService.Status status, String author, String title, PlaybackService.VideoType videoType, long rssItemId, float speed) {
this.current = current;
this.max = max;
this.status = status;
this.author = author;
this.title = title;
this.videoType = videoType;
this.rssItemId = rssItemId;

View file

@ -1,28 +0,0 @@
package de.luhmer.owncloudnewsreader.events.podcast.broadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.greenrobot.eventbus.EventBus;
import de.luhmer.owncloudnewsreader.events.podcast.TogglePlayerStateEvent;
public class PodcastNotificationToggle extends BroadcastReceiver {
public static final String TAG = PodcastNotificationToggle.class.getCanonicalName();
@Override
public void onReceive(Context context, Intent intent) {
Log.v(TAG, "onReceive() called with: context = [" + context + "], intent = [" + intent + "]");
//TODO problem: only the headphone unplug event is triggered. Somehow the headphone plug-in event is not triggered at all..
//TODO expected: receive the headphone plug-in event and trigger the "play" event
if(intent.getAction() != null && intent.getAction().equals("android.media.AUDIO_BECOMING_NOISY")) {
EventBus.getDefault().post(new TogglePlayerStateEvent(TogglePlayerStateEvent.State.Pause));
} else {
EventBus.getDefault().post(new TogglePlayerStateEvent());
}
}
}

View file

@ -4,6 +4,7 @@ import java.io.Serializable;
public abstract class MediaItem implements Serializable {
public long itemId;
public String author;
public String title;
public String favIcon;
public String link;

View file

@ -6,8 +6,9 @@ public class PodcastItem extends MediaItem {
}
public PodcastItem(long itemId, String title, String link, String mimeType, boolean offlineCached, String favIcon, boolean isVideoPodcast) {
public PodcastItem(long itemId, String author, String title, String link, String mimeType, boolean offlineCached, String favIcon, boolean isVideoPodcast) {
this.itemId = itemId;
this.author = author;
this.title = title;
this.link = link;
this.mimeType = mimeType;

View file

@ -2,12 +2,9 @@ package de.luhmer.owncloudnewsreader.model;
public class TTSItem extends MediaItem {
public TTSItem() {
}
public TTSItem(long itemId, String title, String text, String favIcon) {
public TTSItem(long itemId, String author, String title, String text, String favIcon) {
this.itemId = itemId;
this.author = author;
this.title = title;
this.text = text;
this.favIcon = favIcon;

View file

@ -8,11 +8,18 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.service.notification.StatusBarNotification;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.FileProvider;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaButtonReceiver;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageSize;
@ -116,10 +123,17 @@ public class NextcloudNotificationManager {
notificationManager.notify(123, notify);
}
public static NotificationCompat.Builder buildPodcastNotification(Context context, String channelId) {
/**
* Build a notification using the information from the given media session. Makes heavy use
* of {@link MediaMetadataCompat#getDescription()} to extract the appropriate information.
* @param context Context used to construct the notification.
* @param mediaSession Media session to get information.
* @return A pre-built notification with information from the given media session.
*/
public static NotificationCompat.Builder buildPodcastNotification(Context context, String channelId, MediaSessionCompat mediaSession) {
getNotificationManagerAndCreateChannel(context, channelId);
/*
// Creates an explicit intent for an ResultActivity to receive.
Intent resultIntent = new Intent(context, NewsReaderListActivity.class);
// Because clicking the notification opens a new ("special") activity, there's
@ -138,6 +152,57 @@ public class NextcloudNotificationManager {
.setOngoing(true)
.setOnlyAlertOnce(true)
.setContentIntent(resultPendingIntent);
*/
Bitmap bitmapIcon = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher);
MediaControllerCompat controller = mediaSession.getController();
MediaMetadataCompat mediaMetadata = controller.getMetadata();
MediaDescriptionCompat description = mediaMetadata.getDescription();
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId)
/*
.setStyle(new NotificationCompat.MediaStyle()
.setShowActionsInCompactView(
new int[]{playPauseButtonPosition}) // show only play/pause in compact view
.setMediaSession(mSession.getSessionToken()))
*/
//.setUsesChronometer(true)
.setContentTitle(description.getTitle())
.setSmallIcon(R.drawable.ic_notification)
//.setContentText(description.getSubtitle())
//.setContentText(mediaMetadata.getText(MediaMetadataCompat.METADATA_KEY_ARTIST))
//.setSubText(description.getDescription())
//.setLargeIcon(description.getIconBitmap())
.setLargeIcon(bitmapIcon)
.setContentIntent(controller.getSessionActivity())
.setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true);
boolean isPlaying = controller.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING;
builder.addAction(getPlayPauseAction(context, isPlaying));
builder.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
//.setShowActionsInCompactView(0) // show only play/pause in compact view
.setMediaSession(mediaSession.getSessionToken())
.setShowCancelButton(true)
.setCancelButtonIntent(
MediaButtonReceiver.buildMediaButtonPendingIntent(
context, PlaybackStateCompat.ACTION_STOP)));
return builder;
}
private static NotificationCompat.Action getPlayPauseAction(Context context, boolean isPlaying) {
int drawableId = isPlaying ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play;
String actionText = isPlaying ? "Pause" : "Play"; // TODO extract as string resource
PendingIntent pendingIntent = MediaButtonReceiver.buildMediaButtonPendingIntent(context,
isPlaying ? PlaybackStateCompat.ACTION_PAUSE : PlaybackStateCompat.ACTION_PLAY);
return new NotificationCompat.Action(drawableId, actionText, pendingIntent);
}
public static NotificationCompat.Builder buildDownloadPodcastNotification(Context context, String channelId) {
@ -223,6 +288,9 @@ public class NextcloudNotificationManager {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel mChannel = new NotificationChannel(channelId, channelId, importance);
mChannel.setSound(null, null);
mChannel.enableVibration(false);
//mChannel.setShowBadge(false);
//mChannel.enableLights(true);
notificationManager.createNotificationChannel(mChannel);
}

View file

@ -1,17 +1,34 @@
package de.luhmer.owncloudnewsreader.services;
import android.app.Service;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaBrowserServiceCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaButtonReceiver;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.List;
import de.luhmer.owncloudnewsreader.NewsReaderListActivity;
import de.luhmer.owncloudnewsreader.R;
import de.luhmer.owncloudnewsreader.events.podcast.NewPodcastPlaybackListener;
import de.luhmer.owncloudnewsreader.events.podcast.PodcastCompletedEvent;
import de.luhmer.owncloudnewsreader.events.podcast.RegisterVideoOutput;
@ -29,23 +46,27 @@ import de.luhmer.owncloudnewsreader.services.podcast.TTSPlaybackService;
import de.luhmer.owncloudnewsreader.services.podcast.YoutubePlaybackService;
import de.luhmer.owncloudnewsreader.view.PodcastNotification;
public class PodcastPlaybackService extends Service {
import static android.view.KeyEvent.KEYCODE_MEDIA_STOP;
public class PodcastPlaybackService extends MediaBrowserServiceCompat {
public static final String MEDIA_ITEM = "MediaItem";
private static final String TAG = "PodcastPlaybackService";
public static final String PLAYBACK_SPEED_FLOAT = "PLAYBACK_SPEED";
public static final String CURRENT_PODCAST_ITEM_MEDIA_ITEM= "CURRENT_PODCAST_ITEM";
private PodcastNotification podcastNotification;
private EventBus eventBus;
private Handler mHandler;
private PlaybackService mPlaybackService;
private MediaSessionCompat mSession;
public static final float PLAYBACK_SPEEDS[] = { 0.25f, 0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f };
private float currentPlaybackSpeed = 1;
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
public MediaItem getCurrentlyPlayingPodcast() {
if(mPlaybackService != null) {
@ -58,20 +79,18 @@ public class PodcastPlaybackService extends Service {
return mPlaybackService != null;
}
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public PodcastPlaybackService getService() {
// Return this instance of LocalService so clients can call public methods
return PodcastPlaybackService.this;
}
@Nullable
@Override
public BrowserRoot onGetRoot(@NonNull String s, int i, @Nullable Bundle bundle) {
return new MediaBrowserServiceCompat.BrowserRoot(
getString(R.string.app_name),// Name visible in Android Auto
null);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
public void onLoadChildren(@NonNull String s, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
Log.d(TAG, "onLoadChildren() called with: s = [" + s + "], result = [" + result + "]");
result.sendResult(new ArrayList<MediaBrowserCompat.MediaItem>());
}
@Override
@ -98,16 +117,31 @@ public class PodcastPlaybackService extends Service {
mgr.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
podcastNotification = new PodcastNotification(this);
initMediaSessions();
podcastNotification = new PodcastNotification(this, mSession);
mHandler = new Handler();
eventBus = EventBus.getDefault();
eventBus.register(this);
eventBus.post(new PodcastPlaybackServiceStarted());
//eventBus.post(new PodcastPlaybackServiceStarted());
mHandler.postDelayed(mUpdateTimeTask, 0);
setSessionToken(mSession.getSessionToken());
Intent intent = new Intent(this, NewsReaderListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mSession.setSessionActivity(pi);
//startForeground(PodcastNotification.NOTIFICATION_ID, podcastNotification.getNotification());
/*
//Handles headphones coming unplugged. cannot be done through a manifest receiver
IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
registerReceiver(mNoisyReceiver, filter);
*/
}
@Override
@ -119,6 +153,7 @@ public class PodcastPlaybackService extends Service {
mgr.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
}
mHandler.removeCallbacks(mUpdateTimeTask);
podcastNotification.cancel();
super.onDestroy();
@ -127,21 +162,25 @@ public class PodcastPlaybackService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
MediaButtonReceiver.handleIntent(mSession, intent);
if (intent != null) {
if(mPlaybackService != null) {
if (mPlaybackService != null) {
mPlaybackService.destroy();
mPlaybackService = null;
}
mHandler.removeCallbacks(mUpdateTimeTask);
if(intent.hasExtra(MEDIA_ITEM)) {
MediaItem mediaItem = (MediaItem) intent.getSerializableExtra(MEDIA_ITEM);
if (mediaItem instanceof PodcastItem) {
if(((PodcastItem)mediaItem).isYoutubeVideo()) {
if (((PodcastItem) mediaItem).isYoutubeVideo()) {
mPlaybackService = new YoutubePlaybackService(this, podcastStatusListener, mediaItem);
} else {
mPlaybackService = new MediaPlayerPlaybackService(this, podcastStatusListener, mediaItem);
}
} else if(mediaItem instanceof TTSItem) {
} else if (mediaItem instanceof TTSItem) {
mPlaybackService = new TTSPlaybackService(this, podcastStatusListener, mediaItem);
}
@ -150,8 +189,9 @@ public class PodcastPlaybackService extends Service {
mPlaybackService.playbackSpeedChanged(currentPlaybackSpeed);
}
}
return Service.START_STICKY;
return super.onStartCommand(intent, flags, startId);
}
private PlaybackService.PodcastStatusListener podcastStatusListener = new PlaybackService.PodcastStatusListener() {
@ -188,6 +228,7 @@ public class PodcastPlaybackService extends Service {
@Subscribe
public void onEvent(TogglePlayerStateEvent event) {
Log.d(TAG, "onEvent() called with: event = [" + event + "]");
if(event.getState() == TogglePlayerStateEvent.State.Toggle) {
if (isPlaying()) {
Log.v(TAG, "calling pause()");
@ -275,24 +316,29 @@ public class PodcastPlaybackService extends Service {
UpdatePodcastStatusEvent audioPodcastEvent;
if(mPlaybackService == null) {
audioPodcastEvent = new UpdatePodcastStatusEvent(0, 0, PlaybackService.Status.NOT_INITIALIZED, "", PlaybackService.VideoType.None, -1, -1);
audioPodcastEvent = new UpdatePodcastStatusEvent(0, 0, PlaybackService.Status.NOT_INITIALIZED, "", "", PlaybackService.VideoType.None, -1, -1);
} else {
audioPodcastEvent = new UpdatePodcastStatusEvent(
mPlaybackService.getCurrentDuration(),
mPlaybackService.getTotalDuration(),
mPlaybackService.getStatus(),
mPlaybackService.getMediaItem().link,
mPlaybackService.getMediaItem().title,
mPlaybackService.getVideoType(),
mPlaybackService.getMediaItem().itemId,
getPlaybackSpeed());
}
eventBus.post(audioPodcastEvent);
if(audioPodcastEvent.isPlaying()) {
startForeground(PodcastNotification.NOTIFICATION_ID, podcastNotification.getNotification());
} else {
stopForeground(false);
}
}
public class PodcastPlaybackServiceStarted {
}
//public class PodcastPlaybackServiceStarted { }
PhoneStateListener phoneStateListener = new PhoneStateListener() {
@Override
@ -308,4 +354,91 @@ public class PodcastPlaybackService extends Service {
super.onCallStateChanged(state, incomingNumber);
}
};
private final class MediaSessionCallback extends MediaSessionCompat.Callback {
@Override
public void onPlay() {
EventBus.getDefault().post(new TogglePlayerStateEvent());
}
@Override
public void onPause() {
EventBus.getDefault().post(new TogglePlayerStateEvent());
}
@Override
public void onCommand(String command, Bundle extras, ResultReceiver cb) {
if (command.equals(PLAYBACK_SPEED_FLOAT)) {
Bundle b = new Bundle();
b.putFloat(PLAYBACK_SPEED_FLOAT, currentPlaybackSpeed);
cb.send(0, b);
} else if(command.equals(CURRENT_PODCAST_ITEM_MEDIA_ITEM)) {
Bundle b = new Bundle();
b.putSerializable(CURRENT_PODCAST_ITEM_MEDIA_ITEM, mPlaybackService.getMediaItem());
cb.send(0, b);
}
super.onCommand(command, extras, cb);
}
@Override
public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
Log.d(TAG, mediaButtonEvent.getAction());
if(mediaButtonEvent.hasExtra("android.intent.extra.KEY_EVENT")) {
KeyEvent keyEvent = mediaButtonEvent.getParcelableExtra("android.intent.extra.KEY_EVENT");
Log.d(TAG, keyEvent.toString());
// Stop requested (e.g. notification was swiped away)
if(keyEvent.getKeyCode() == KEYCODE_MEDIA_STOP) {
pause();
stopSelf();
/*
boolean isPlaying = mSession.getController().getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING;
if(isPlaying) {
EventBus.getDefault().post(new TogglePlayerStateEvent());
}
*/
}
}
return super.onMediaButtonEvent(mediaButtonEvent);
}
}
private void initMediaSessions() {
//String packageName = PodcastNotificationToggle.class.getPackage().getName();
//ComponentName receiver = new ComponentName(packageName, PodcastNotificationToggle.class.getName());
ComponentName mediaButtonReceiver = new ComponentName(this, MediaButtonReceiver.class);
mSession = new MediaSessionCompat(this, "PlayerService", mediaButtonReceiver, null);
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PAUSED, 0, 0)
.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE).build());
mSession.setCallback(new MediaSessionCallback());
//Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
//mediaButtonIntent.setClass(mContext, MediaButtonReceiver.class);
//PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, mediaButtonIntent, 0);
//mSession.setMediaButtonReceiver(pendingIntent);
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.requestAudioFocus(new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
// Ignore
}
}, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
//MediaControllerCompat controller = mSession.getController();
//mSession.setActive(true);
mSession.setMetadata(new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "")
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "")
.build());
}
}

View file

@ -24,7 +24,7 @@ public class MediaPlayerPlaybackService extends PlaybackService {
private View parentResizableView;
public MediaPlayerPlaybackService(final Context context, PodcastStatusListener podcastStatusListener, MediaItem mediaItem) {
super(context, podcastStatusListener, mediaItem);
super(podcastStatusListener, mediaItem);
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {

View file

@ -1,7 +1,5 @@
package de.luhmer.owncloudnewsreader.services.podcast;
import android.content.Context;
import de.luhmer.owncloudnewsreader.model.MediaItem;
/**
@ -22,7 +20,7 @@ public abstract class PlaybackService {
private PodcastStatusListener podcastStatusListener;
private MediaItem mediaItem;
public PlaybackService(Context context, PodcastStatusListener podcastStatusListener, MediaItem mediaItem) {
public PlaybackService(PodcastStatusListener podcastStatusListener, MediaItem mediaItem) {
this.podcastStatusListener = podcastStatusListener;
this.mediaItem = mediaItem;
}

View file

@ -18,7 +18,7 @@ public class TTSPlaybackService extends PlaybackService implements TextToSpeech.
private TextToSpeech ttsController;
public TTSPlaybackService(Context context, PodcastStatusListener podcastStatusListener, MediaItem mediaItem) {
super(context, podcastStatusListener, mediaItem);
super(podcastStatusListener, mediaItem);
try {
ttsController = new TextToSpeech(context, this);

View file

@ -2,11 +2,7 @@ package de.luhmer.owncloudnewsreader.view;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.support.v4.media.MediaMetadataCompat;
@ -14,17 +10,12 @@ import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import java.util.Locale;
import de.luhmer.owncloudnewsreader.R;
import de.luhmer.owncloudnewsreader.events.podcast.TogglePlayerStateEvent;
import de.luhmer.owncloudnewsreader.events.podcast.UpdatePodcastStatusEvent;
import de.luhmer.owncloudnewsreader.events.podcast.broadcastreceiver.PodcastNotificationToggle;
import de.luhmer.owncloudnewsreader.model.MediaItem;
import de.luhmer.owncloudnewsreader.notification.NextcloudNotificationManager;
import de.luhmer.owncloudnewsreader.services.PodcastPlaybackService;
@ -41,21 +32,20 @@ public class PodcastNotification {
private final Context mContext;
private final NotificationManager notificationManager;
private final NotificationCompat.Builder notificationBuilder;
private NotificationCompat.Builder notificationBuilder;
private final String CHANNEL_ID = "Podcast Notification";
//private MediaSessionManager mManager;
private MediaSessionCompat mSession;
//private MediaControllerCompat mController;
private int lastDrawableId = -1;
private PlaybackService.Status lastStatus = PlaybackService.Status.NOT_INITIALIZED;
public final static int NOTIFICATION_ID = 1111;
public PodcastNotification(Context context) {
public PodcastNotification(Context context, MediaSessionCompat session) {
this.mContext = context;
this.mSession = session;
this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
this.notificationBuilder = NextcloudNotificationManager.buildPodcastNotification(mContext, CHANNEL_ID);
this.notificationBuilder = NextcloudNotificationManager.buildPodcastNotification(mContext, CHANNEL_ID, mSession);
EventBus.getDefault().register(this);
}
@ -66,7 +56,6 @@ public class PodcastNotification {
}
}
@Subscribe
public void onEvent(UpdatePodcastStatusEvent podcast) {
if(mSession == null) {
@ -74,12 +63,12 @@ public class PodcastNotification {
return;
}
int drawableId = podcast.isPlaying() ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play;
String actionText = podcast.isPlaying() ? "Pause" : "Play";
if(lastDrawableId != drawableId) {
lastDrawableId = drawableId;
if (podcast.getStatus() != lastStatus) {
lastStatus = podcast.getStatus();
/*
notificationBuilder.setContentTitle(podcast.getTitle());
notificationBuilder.mActions.clear();
notificationBuilder.addAction(
@ -88,7 +77,9 @@ public class PodcastNotification {
PendingIntent.getBroadcast(mContext, 0, new Intent(mContext, PodcastNotificationToggle.class),
PendingIntent.FLAG_ONE_SHOT));
*/
/*
if(podcast.isPlaying()) {
//Prevent the Podcast Player from getting killed because of low memory
//For more info see: http://developer.android.com/reference/android/app/Service.html#startForeground(int, android.app.Notification)
@ -100,6 +91,7 @@ public class PodcastNotification {
notificationBuilder.setOngoing(false); // cancelable
}
*/
//Lock screen notification
/*
@ -108,7 +100,9 @@ public class PodcastNotification {
.build());
*/
if(podcast.isPlaying()) {
mSession.setActive(true);
if (podcast.isPlaying()) {
mSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, podcast.getCurrent(), 1.0f)
.setActions(PlaybackStateCompat.ACTION_PAUSE).build());
@ -117,6 +111,18 @@ public class PodcastNotification {
.setState(PlaybackStateCompat.STATE_PAUSED, podcast.getCurrent(), 0.0f)
.setActions(PlaybackStateCompat.ACTION_PLAY).build());
}
//mSession.setActive(podcast.isPlaying());
notificationBuilder = NextcloudNotificationManager.buildPodcastNotification(mContext, CHANNEL_ID, mSession);
//int drawableId = podcast.isPlaying() ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play;
//String actionText = podcast.isPlaying() ? "Pause" : "Play";
//notificationBuilder.addAction(new NotificationCompat.Action(drawableId, actionText, intent));
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
@ -156,76 +162,42 @@ public class PodcastNotification {
}
public void podcastChanged() {
initMediaSessions();
}
private void initMediaSessions() {
String packageName = PodcastNotificationToggle.class.getPackage().getName();
ComponentName receiver = new ComponentName(packageName, PodcastNotificationToggle.class.getName());
mSession = new MediaSessionCompat(mContext, "PlayerService", receiver, null);
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PAUSED, 0, 0)
.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE).build());
MediaItem podcastItem = ((PodcastPlaybackService)mContext).getCurrentlyPlayingPodcast();
/*
String favIconUrl = podcastItem.favIcon;
DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().
showImageOnLoading(R.drawable.default_feed_icon_light).
showImageForEmptyUri(R.drawable.default_feed_icon_light).
showImageOnFail(R.drawable.default_feed_icon_light).
build();
*/
//TODO networkOnMainThreadExceptionHere!
//Bitmap bmpAlbumArt = ImageLoader.getInstance().loadImageSync(favIconUrl, displayImageOptions);
mSession.setMetadata(new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "Test")
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "Test")
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Test")
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, podcastItem.author)
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, podcastItem.title)
.build());
/*
mSession.setMetadata(new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "NA")
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "")
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "NA")
//.putString(MediaMetadataCompat.METADATA_KEY_TITLE, podcastItem.title)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, 100)
//.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bmpAlbumArt)
/* .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART,
BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher)) */
//.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher))
.build());
*/
mSession.setCallback(new MediaSessionCallback());
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
audioManager.requestAudioFocus(new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
// Ignore
}
}, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
//MediaControllerCompat controller = mSession.getController();
mSession.setActive(true);
this.notificationBuilder = NextcloudNotificationManager.buildPodcastNotification(mContext, CHANNEL_ID, mSession);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
public Notification getNotification() {
return notificationBuilder.build();
}
private final class MediaSessionCallback extends MediaSessionCompat.Callback {
@Override
public void onPlay() {
EventBus.getDefault().post(new TogglePlayerStateEvent());
}
@Override
public void onPause() {
EventBus.getDefault().post(new TogglePlayerStateEvent());
}
}
}

View file

@ -101,7 +101,7 @@
android:textAlignment="viewStart"
android:ellipsize="end"
tools:text="Body"
android:textColor="@color/material_grey_600"
android:textColor="@color/text_medium_emphasis"
android:textSize="14sp"
android:autoLink="none"
android:layout_below="@+id/summary"

View file

@ -97,7 +97,7 @@
android:textAlignment="viewStart"
android:ellipsize="end"
tools:text="Body"
android:textColor="@color/material_grey_600"
android:textColor="@color/text_medium_emphasis"
android:textSize="14sp"
android:autoLink="none"
android:layout_below="@+id/summary"

View file

@ -71,7 +71,7 @@
android:layout_marginBottom="8dp"
android:maxLines="5"
android:textAlignment="viewStart"
android:textColor="@color/material_grey_600"
android:textColor="@color/text_medium_emphasis"
android:textSize="15sp"
android:textStyle="normal"
tools:text="Anruf-Info djkas dhask dhas dashdajs dha dhas dhas djka a jsa das djsa djas dash djas dashdja sljda dhjas ja dkla da ja da djas djkasas jkas dklas dsa djla hkjdsahkdas hd ashjdas jsak dsada" />

View file

@ -152,6 +152,9 @@
<string name="no_podcast_selected">Kein Podcast ausgewählt</string>
<string name="no_chapters_available">Keine Kapitel verfügbar</string>
<string name="podcast_playback_speed_dialog_title">Wiedergabegeschwindigkeit</string>
<string name="notification_downloading_podcast_title">Podcast wird herunterladen</string>
<!-- Settings for Display -->
<string name="pref_header_display">Anzeige</string>
<string name="pref_title_app_theme">Nachtmodus</string>

View file

@ -153,6 +153,9 @@
<string name="no_podcast_selected">Benötigt keine Übersetzung. Für Android wird nur die formelle Übersetzung verwendet (de_DE).</string>
<string name="no_chapters_available">Benötigt keine Übersetzung. Für Android wird nur die formelle Übersetzung verwendet (de_DE).</string>
<string name="podcast_playback_speed_dialog_title">Benötigt keine Übersetzung. Für Android wird nur die formelle Übersetzung verwendet (de_DE).</string>
<string name="notification_downloading_podcast_title">Podcast wird herunterladen</string>
<!-- Settings for Display -->
<string name="pref_header_display">Benötigt keine Übersetzung. Für Android wird nur die formelle Übersetzung verwendet (de_DE).</string>
<string name="pref_title_app_theme">Benötigt keine Übersetzung. Für Android wird nur die formelle Übersetzung verwendet (de_DE).</string>

View file

@ -25,6 +25,8 @@
<string name="menu_StartImageCaching">Descargar imágenes</string>
<string name="menu_downloadMoreItems">Descargar más artículos</string>
<string name="tv_showing_cached_version">Mostrando versión en caché</string>
<!-- Action Bar Items -->
<string name="action_starred">Destacado</string>
<string name="action_read">Leer</string>
@ -37,6 +39,7 @@
<string name="action_sync_settings">Ajustes de sincronización</string>
<string name="action_add_new_feed">Añadir nueva fuente</string>
<string name="action_textToSpeech">Recopilar información</string>
<string name="action_download_articles_offline">Descargar los artículos sin conexión</string>
<plurals name="notification_new_items_ticker">
<item quantity="one">Tienes %d elemento nuevo sin leer</item>
<item quantity="other">Tienes %d elementos nuevos sin leer</item>
@ -149,6 +152,9 @@
<string name="no_podcast_selected">Ningún podcast seleccionado</string>
<string name="no_chapters_available">No hay capítulos disponibles</string>
<string name="podcast_playback_speed_dialog_title">Velocidad de reproducción</string>
<string name="notification_downloading_podcast_title">Descargando podcast</string>
<!-- Settings for Display -->
<string name="pref_header_display">Mostrar</string>
<string name="pref_title_app_theme">Modo Oscuro</string>
@ -172,6 +178,12 @@
<string name="pref_display_browser_built_in">Navegador integrado</string>
<string name="pref_display_browser_external">Navegador externo</string>
<string name="pref_display_feed_list_layout_thumbnails">Miniaturas</string>
<string name="pref_display_feed_list_layout_simple_text">Texto sencillo</string>
<string name="pref_display_feed_list_layout_full_text">Texto completo</string>
<string name="pref_display_feed_list_layout_web_layout">Disposición web</string>
<string name="pref_display_feed_list_layout_card_view">Vista de tarjetas</string>
<!-- font size scaling definitions -->
<string name="pref_display_font_size_s">Pequeño</string>
<string name="pref_display_font_size_d">Por defecto</string>

View file

@ -25,6 +25,8 @@
<string name="menu_StartImageCaching">Télécharger les images</string>
<string name="menu_downloadMoreItems">+ d\'articles</string>
<string name="tv_showing_cached_version">Affichage de la version en cache</string>
<!-- Action Bar Items -->
<string name="action_starred">Favori</string>
<string name="action_read">Lu</string>
@ -37,6 +39,7 @@
<string name="action_sync_settings">Paramètres de synchronisation</string>
<string name="action_add_new_feed">Ajouter un nouveau flux</string>
<string name="action_textToSpeech">Lire à haute voix</string>
<string name="action_download_articles_offline">Télécharger les articles hors ligne</string>
<plurals name="notification_new_items_ticker">
<item quantity="one">Il y a %d nouvel article non lu</item>
<item quantity="other">Il y a %d nouveaux articles non lus</item>
@ -149,6 +152,9 @@
<string name="no_podcast_selected">Aucun podcast sélectionné</string>
<string name="no_chapters_available">Aucun chapitre disponible</string>
<string name="podcast_playback_speed_dialog_title">Vitesse de lecture</string>
<string name="notification_downloading_podcast_title">Téléchargement du podcast</string>
<!-- Settings for Display -->
<string name="pref_header_display">Affichage</string>
<string name="pref_title_app_theme">Mode nuit</string>
@ -172,7 +178,7 @@
<string name="pref_display_browser_built_in">Navigateur intégré</string>
<string name="pref_display_browser_external">Navigateur externe</string>
<string name="pref_display_feed_list_layout_thumbnails">Miniatures</string>
<string name="pref_display_feed_list_layout_thumbnails">Vignettes</string>
<!-- font size scaling definitions -->
<string name="pref_display_font_size_s">Petit</string>
<string name="pref_display_font_size_d">Par défaut</string>

View file

@ -152,6 +152,9 @@
<string name="no_podcast_selected">Nessun podcast selezionato</string>
<string name="no_chapters_available">Nessun capitolo disponibile</string>
<string name="podcast_playback_speed_dialog_title">Velocità di riproduzione</string>
<string name="notification_downloading_podcast_title">Scaricamento podcast</string>
<!-- Settings for Display -->
<string name="pref_header_display">Schermo</string>
<string name="pref_title_app_theme">Modalità notte</string>

View file

@ -26,6 +26,7 @@
<string name="action_starred">共有</string>
<string name="action_read">読込</string>
<string name="action_playPodacst">Podcast を再生</string>
<string name="action_openInBrowser">ブラウザで開く</string>
<string name="action_Share">共有</string>
<string name="action_login">サーバー設定</string>
<string name="action_save">保存</string>
@ -101,6 +102,10 @@
<string name="pref_header_general">一般</string>
<string name="pref_title_general_sort_order">ソート順</string>
<string name="pref_general_sort_order_new_old">新 -> 旧</string>
<string name="pref_general_sort_order_old_new">旧 -> 新</string>
<string name="pref_general_search_in_title">タイトル</string>
<string name="dialog_clearing_cache">キャッシュをクリアしています。</string>
<string name="dialog_clearing_cache_please_wait">キャッシュをクリアしています…しばらくお待ちください。</string>
<string name="reset_cache_unsaved_changes">未同期の変更があります。とにかくキャッシュをリセットしますか?</string>
@ -120,11 +125,38 @@
<!-- Podcast -->
<string name="no_podcast_selected">Podcast が選択されていません</string>
<string name="no_chapters_available">チャプターがありません</string>
<string name="podcast_playback_speed_dialog_title">再生速度</string>
<!-- Settings for Display -->
<string name="pref_header_display">表示</string>
<string name="pref_title_app_theme">ナイトモード</string>
<string name="pref_title_feed_list_layout">フィードリストのレイアウト</string>
<string name="pref_title_font_size">フォントサイズ</string>
<string name="pref_display_browser">ブラウザ</string>
<string name="pref_display_apptheme_auto">ライト / ダーク(時刻に基づく)</string>
<string name="pref_display_apptheme_light">ライト</string>
<string name="pref_display_apptheme_dark">ダーク</string>
<string name="pref_display_browser_cct">内蔵のChromeカスタムタブ</string>
<string name="pref_display_browser_built_in">組み込みブラウザ</string>
<string name="pref_display_browser_external">外部ブラウザ</string>
<string name="pref_display_feed_list_layout_thumbnails">サムネイル</string>
<string name="pref_display_feed_list_layout_web_layout">ウェブレイアウト</string>
<string name="pref_display_feed_list_layout_card_view">カード表示</string>
<!-- font size scaling definitions -->
<string name="pref_display_font_size_s"></string>
<string name="pref_display_font_size_d">デフォルト</string>
<string name="pref_display_font_size_l"></string>
<string name="pref_display_font_size_xl">巨大</string>
<string name="content_desc_play">再生</string>
<string name="content_desc_pause">一時停止</string>
<string name="content_desc_playback_speed">再生速度</string>
<string name="content_desc_rewind">巻戻し</string>
<string name="content_desc_forward">早送り</string>
<string name="content_desc_expand">展開</string>
@ -158,4 +190,20 @@
<string name="pref_data_sync_image_cache_never">しない</string>
<string name="pref_data_sync_image_cache_wifi_only">WiFiのみ</string>
<string name="pref_data_sync_image_cache_wifi_and_mobile">WiFi &amp; モバイル</string>
<string name="pref_data_sync_image_cache_ask">WiFi接続がない場合は尋ねる</string>
<string name="array_sync_interval_min_5">5 分</string>
<string name="array_sync_interval_min_15">15 分</string>
<string name="array_sync_interval_min_30">30 分</string>
<string name="array_sync_interval_min_45">45 分</string>
<string name="array_sync_interval_hour_1">1 時間</string>
<string name="array_sync_interval_hour_2">2 時間</string>
<string name="array_sync_interval_hour_3">3 時間</string>
<string name="array_sync_interval_hour_6">6 時間</string>
<string name="array_sync_interval_hour_12">12 時間</string>
<string name="array_sync_interval_hour_24">24 時間</string>
</resources>

View file

@ -22,6 +22,8 @@
<color name="owncloudBlue">#1D2D44</color>
<color name="nextcloudBlue">#006AA3</color>
<color name="text_medium_emphasis">#a0ffffff</color>
<color name="colorPrimary">@color/nextcloudBlue</color>
<color name="colorPrimaryDark">@color/nextcloudBlue</color>
<color name="colorAccent">#8698af</color>

View file

@ -25,6 +25,8 @@
<string name="menu_StartImageCaching">Downloaden afbeeldingen</string>
<string name="menu_downloadMoreItems">Download meer berichten</string>
<string name="tv_showing_cached_version">Tonen van cached versie</string>
<!-- Action Bar Items -->
<string name="action_starred">Gemarkeerd</string>
<string name="action_read">Lees</string>
@ -37,6 +39,7 @@
<string name="action_sync_settings">Sync Settings</string>
<string name="action_add_new_feed">Voeg nieuwe feed toe</string>
<string name="action_textToSpeech">Voorlezen</string>
<string name="action_download_articles_offline">Download artikels offline</string>
<plurals name="notification_new_items_ticker">
<item quantity="one">U hebt %d nieuw ongelezen bericht</item>
<item quantity="other">Je hebt %d nieuwe ongelezen berichten</item>
@ -149,9 +152,12 @@
<string name="no_podcast_selected">Geen podcast geselecteerd</string>
<string name="no_chapters_available">Geen hoofdstukken beschikbaar</string>
<string name="podcast_playback_speed_dialog_title">Afspeelsnelheid</string>
<string name="notification_downloading_podcast_title">Downloaden podcast</string>
<!-- Settings for Display -->
<string name="pref_header_display">Weergeven</string>
<string name="pref_title_app_theme">Nochtmodus</string>
<string name="pref_title_app_theme">Nachtmodus</string>
<string name="pref_title_feed_list_layout">Feed lijst overzicht</string>
<string name="pref_title_font_size">Fontgrootte</string>
<string name="pref_display_browser">Browser</string>
@ -176,6 +182,8 @@
<string name="pref_display_feed_list_layout_simple_text">Verkorte tekst</string>
<string name="pref_display_feed_list_layout_full_text">Volledige tekst</string>
<string name="pref_display_feed_list_layout_web_layout">Weblayout</string>
<string name="pref_display_feed_list_layout_card_view">Kaart weergave</string>
<!-- font size scaling definitions -->
<string name="pref_display_font_size_s">Klein</string>
<string name="pref_display_font_size_d">Standaard</string>
@ -207,7 +215,7 @@
<!-- Login Dialog -->
<string name="login_dialog_title_error">Fout</string>
<string name="login_dialog_text_news_app_not_installed_on_server" formatted="true">het lijkt erop dat je nieuwsapp niet is geïnstalleerd of geactiveerd op je server. Volg eerst de instructies hier om de nieuwsapp op je server te installeren: %1$s</string>
<string name="login_dialog_text_news_app_not_installed_on_server" formatted="true">Het lijkt erop dat je nieuwsapp niet is geïnstalleerd of geactiveerd op je server. Volg eerst de instructies hier om de nieuwsapp op je server te installeren: %1$s</string>
<string name="login_dialog_text_something_went_wrong">Er ging iets verkeerd :(</string>
<string name="login_dialog_text_zero_version_code">De Web News App gaf Versie \"0\". Bekijk alsjeblieft het volgende bug report: https://github.com/nextcloud/news/issues/5#issuecomment-242883795</string>
<string name="login_dialog_text_not_compatible">Deze app versie is niet compatibel met je Nextcloud Nieuws App. Werk de Nieuws App en het Appframework bij.</string>

View file

@ -152,6 +152,9 @@
<string name="no_podcast_selected">Nenhum podcast selecionado</string>
<string name="no_chapters_available">Nenhum capítulo disponível</string>
<string name="podcast_playback_speed_dialog_title">Velocidade do Playback</string>
<string name="notification_downloading_podcast_title">Baixando podcast</string>
<!-- Settings for Display -->
<string name="pref_header_display">Exibir</string>
<string name="pref_title_app_theme">Modo Noturno</string>

View file

@ -157,6 +157,9 @@
<string name="no_podcast_selected">Није изабрана подемисија</string>
<string name="no_chapters_available">Нема доступних поглавља</string>
<string name="podcast_playback_speed_dialog_title">Брзина пуштања</string>
<string name="notification_downloading_podcast_title">Преузимам подкаст</string>
<!-- Settings for Display -->
<string name="pref_header_display">Приказ</string>
<string name="pref_title_app_theme">Ноћни режим</string>

View file

@ -152,6 +152,9 @@
<string name="no_podcast_selected">Herhangi bir podcast seçilmemiş</string>
<string name="no_chapters_available">Kullanılabilecek bir bölüm yok</string>
<string name="podcast_playback_speed_dialog_title">Oynatma Hızı</string>
<string name="notification_downloading_podcast_title">Podcast indiriliyor</string>
<!-- Settings for Display -->
<string name="pref_header_display">Görüntüle</string>
<string name="pref_title_app_theme">Gece Kipi</string>

View file

@ -16,7 +16,7 @@
<color name="options_menu_item_text">@color/options_menu_item</color>
<color name="material_red_600" tools:override="true">#e53935</color>
<color name="text_medium_emphasis">#a0000000</color>
<!-- see also assets/web.css -->
<color name="news_detail_background_color">#eeeeee</color>
@ -51,4 +51,6 @@
<color name="material_grey_700" tools:override="true">#ff717171</color>
<color name="material_grey_800" tools:override="true">#424242</color>
<color name="material_grey_900" tools:override="true">#212121</color>
<color name="material_red_600" tools:override="true">#e53935</color>
</resources>

View file

@ -11,7 +11,7 @@ import de.luhmer.owncloudnewsreader.model.MediaItem;
public class YoutubePlaybackService extends PlaybackService {
public YoutubePlaybackService(Context context, PodcastStatusListener podcastStatusListener, MediaItem mediaItem) {
super(context, podcastStatusListener, mediaItem);
super(podcastStatusListener, mediaItem);
setStatus(Status.FAILED);
}