mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
Preparation to music streaming - Picking from Gelli
This commit is contained in:
parent
a28ad27288
commit
820f783d01
18 changed files with 1921 additions and 87 deletions
|
|
@ -30,7 +30,6 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
dataBinding true
|
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +57,8 @@ dependencies {
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.paging:paging-runtime-ktx:2.1.2'
|
implementation 'androidx.paging:paging-runtime-ktx:2.1.2'
|
||||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
|
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
|
||||||
|
implementation 'androidx.palette:palette:1.0.0'
|
||||||
|
|
||||||
|
|
||||||
// Android Material
|
// Android Material
|
||||||
implementation 'com.google.android.material:material:1.2.1'
|
implementation 'com.google.android.material:material:1.2.1'
|
||||||
|
|
@ -74,10 +75,11 @@ dependencies {
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
|
implementation 'com.github.bumptech.glide:okhttp3-integration:4.11.0'
|
||||||
implementation "com.github.woltapp:blurhash:f41a23cc50"
|
implementation "com.github.woltapp:blurhash:f41a23cc50"
|
||||||
|
|
||||||
// Exoplayer
|
// Exoplayer
|
||||||
implementation 'com.google.android.exoplayer:exoplayer:2.12.2'
|
implementation 'com.google.android.exoplayer:exoplayer:2.11.4'
|
||||||
|
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
||||||
annotationProcessor "androidx.room:room-compiler:2.2.5"
|
annotationProcessor "androidx.room:room-compiler:2.2.5"
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.cappielloantonio.play">
|
package="com.cappielloantonio.play">
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
tools:ignore="ScopedStorage" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|
@ -23,5 +23,7 @@
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<service android:name=".service.AudioDownloadService"/>
|
||||||
|
<service android:name=".service.AudioPlayerService"/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.cappielloantonio.play.glide.palette;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
import androidx.palette.graphics.Palette;
|
||||||
|
|
||||||
|
public class BitmapPaletteWrapper {
|
||||||
|
private final Bitmap bitmap;
|
||||||
|
private final Palette palette;
|
||||||
|
|
||||||
|
public BitmapPaletteWrapper(Bitmap bitmap, Palette palette) {
|
||||||
|
this.bitmap = bitmap;
|
||||||
|
this.palette = palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap getBitmap() {
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Palette getPalette() {
|
||||||
|
return palette;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2007 The Android Open Source Project Licensed under the Apache
|
||||||
|
* License, Version 2.0 (the "License"); you may not use this file except in
|
||||||
|
* compliance with the License. You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
|
||||||
|
* or agreed to in writing, software distributed under the License is
|
||||||
|
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the specific language
|
||||||
|
* governing permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Modified for Phonograph by Karim Abou Zeid (kabouzeid).
|
||||||
|
|
||||||
|
package com.cappielloantonio.play.service;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.os.PowerManager.WakeLock;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import com.cappielloantonio.play.BuildConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to control headset playback.
|
||||||
|
* Single press: pause/resume
|
||||||
|
* Double press: next track
|
||||||
|
* Triple press: previous track
|
||||||
|
*/
|
||||||
|
public class MediaButtonIntentReceiver extends BroadcastReceiver {
|
||||||
|
private static final boolean DEBUG = BuildConfig.DEBUG;
|
||||||
|
public static final String TAG = MediaButtonIntentReceiver.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final int MSG_HEADSET_DOUBLE_CLICK_TIMEOUT = 2;
|
||||||
|
|
||||||
|
private static final int DOUBLE_CLICK = 400;
|
||||||
|
|
||||||
|
private static WakeLock mWakeLock = null;
|
||||||
|
private static int mClickCounter = 0;
|
||||||
|
private static long mLastClickTime = 0;
|
||||||
|
|
||||||
|
@SuppressLint("HandlerLeak")
|
||||||
|
private static Handler mHandler = new Handler() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(final Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_HEADSET_DOUBLE_CLICK_TIMEOUT:
|
||||||
|
final int clickCount = msg.arg1;
|
||||||
|
final String command;
|
||||||
|
|
||||||
|
if (DEBUG) Log.v(TAG, "Handling headset click, count = " + clickCount);
|
||||||
|
switch (clickCount) {
|
||||||
|
case 1:
|
||||||
|
command = MusicService.ACTION_TOGGLE;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
command = MusicService.ACTION_SKIP;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
command = MusicService.ACTION_REWIND;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
command = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command != null) {
|
||||||
|
final Context context = (Context) msg.obj;
|
||||||
|
startService(context, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseWakeLockIfHandlerIdle();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(final Context context, final Intent intent) {
|
||||||
|
if (DEBUG) Log.v(TAG, "Received intent: " + intent);
|
||||||
|
if (handleIntent(context, intent) && isOrderedBroadcast()) {
|
||||||
|
abortBroadcast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean handleIntent(final Context context, final Intent intent) {
|
||||||
|
final String intentAction = intent.getAction();
|
||||||
|
if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
|
||||||
|
final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
|
||||||
|
if (event == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int keycode = event.getKeyCode();
|
||||||
|
final int action = event.getAction();
|
||||||
|
|
||||||
|
// fallback to system time if event time is not available
|
||||||
|
final long eventTime = event.getEventTime() != 0
|
||||||
|
? event.getEventTime()
|
||||||
|
: System.currentTimeMillis();
|
||||||
|
|
||||||
|
String command = null;
|
||||||
|
switch (keycode) {
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||||
|
command = MusicService.ACTION_STOP;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||||
|
command = MusicService.ACTION_TOGGLE;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
||||||
|
command = MusicService.ACTION_SKIP;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
||||||
|
command = MusicService.ACTION_REWIND;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
||||||
|
command = MusicService.ACTION_PAUSE;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
||||||
|
command = MusicService.ACTION_PLAY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command != null) {
|
||||||
|
if (action == KeyEvent.ACTION_DOWN) {
|
||||||
|
if (event.getRepeatCount() == 0) {
|
||||||
|
// Only consider the first event in a sequence, not the repeat events,
|
||||||
|
// so that we don't trigger in cases where the first event went to
|
||||||
|
// a different app (e.g. when the user ends a phone call by
|
||||||
|
// long pressing the headset button)
|
||||||
|
|
||||||
|
// The service may or may not be running, but we need to send it
|
||||||
|
// a command.
|
||||||
|
if (keycode == KeyEvent.KEYCODE_HEADSETHOOK || keycode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
|
||||||
|
if (eventTime - mLastClickTime >= DOUBLE_CLICK) {
|
||||||
|
mClickCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mClickCounter++;
|
||||||
|
if (DEBUG) Log.v(TAG, "Got headset click, count = " + mClickCounter);
|
||||||
|
mHandler.removeMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT);
|
||||||
|
|
||||||
|
Message msg = mHandler.obtainMessage(
|
||||||
|
MSG_HEADSET_DOUBLE_CLICK_TIMEOUT, mClickCounter, 0, context);
|
||||||
|
|
||||||
|
long delay = mClickCounter < 3 ? DOUBLE_CLICK : 0;
|
||||||
|
if (mClickCounter >= 3) {
|
||||||
|
mClickCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastClickTime = eventTime;
|
||||||
|
acquireWakeLockAndSendMessage(context, msg, delay);
|
||||||
|
} else {
|
||||||
|
startService(context, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void startService(Context context, String command) {
|
||||||
|
final Intent intent = new Intent(context, MusicService.class);
|
||||||
|
intent.setAction(command);
|
||||||
|
try {
|
||||||
|
// IMPORTANT NOTE: (kind of a hack)
|
||||||
|
// on Android O and above the following crashes when the app is not running
|
||||||
|
// there is no good way to check whether the app is running so we catch the exception
|
||||||
|
// we do not always want to use startForegroundService() because then one gets an ANR
|
||||||
|
// if no notification is displayed via startForeground()
|
||||||
|
// according to Play analytics this happens a lot, I suppose for example if command = PAUSE
|
||||||
|
context.startService(intent);
|
||||||
|
} catch (IllegalStateException ignored) {
|
||||||
|
ContextCompat.startForegroundService(context, intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void acquireWakeLockAndSendMessage(Context context, Message msg, long delay) {
|
||||||
|
if (mWakeLock == null) {
|
||||||
|
Context appContext = context.getApplicationContext();
|
||||||
|
PowerManager pm = (PowerManager) appContext.getSystemService(Context.POWER_SERVICE);
|
||||||
|
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getName());
|
||||||
|
mWakeLock.setReferenceCounted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) Log.v(TAG, "Acquiring wake lock and sending " + msg.what);
|
||||||
|
|
||||||
|
mWakeLock.acquire(10000);
|
||||||
|
mHandler.sendMessageDelayed(msg, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void releaseWakeLockIfHandlerIdle() {
|
||||||
|
if (mHandler.hasMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)) {
|
||||||
|
if (DEBUG) Log.v(TAG, "Handler still has messages pending, not releasing wake lock");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mWakeLock != null) {
|
||||||
|
if (DEBUG) Log.v(TAG, "Releasing wake lock");
|
||||||
|
|
||||||
|
mWakeLock.release();
|
||||||
|
mWakeLock = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,290 @@
|
||||||
|
package com.cappielloantonio.play.service;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.cappielloantonio.play.R;
|
||||||
|
import com.cappielloantonio.play.model.Song;
|
||||||
|
import com.cappielloantonio.play.service.playback.Playback;
|
||||||
|
import com.cappielloantonio.play.util.MusicUtil;
|
||||||
|
import com.cappielloantonio.play.util.PreferenceUtil;
|
||||||
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.Player;
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
|
||||||
|
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.upstream.FileDataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.cache.CacheDataSink;
|
||||||
|
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
|
||||||
|
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.Dispatcher;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
public class MultiPlayer implements Playback {
|
||||||
|
public static final String TAG = MultiPlayer.class.getSimpleName();
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final OkHttpClient httpClient;
|
||||||
|
|
||||||
|
private SimpleExoPlayer exoPlayer;
|
||||||
|
private ConcatenatingMediaSource mediaSource;
|
||||||
|
|
||||||
|
private final SimpleCache simpleCache;
|
||||||
|
private final DataSource.Factory dataSource;
|
||||||
|
|
||||||
|
private PlaybackCallbacks callbacks;
|
||||||
|
|
||||||
|
private boolean isReady = false;
|
||||||
|
private boolean isPlaying = false;
|
||||||
|
|
||||||
|
private boolean requestPlay = false;
|
||||||
|
private int requestProgress = 0;
|
||||||
|
|
||||||
|
private final ExoPlayer.EventListener eventListener = new ExoPlayer.EventListener() {
|
||||||
|
@Override
|
||||||
|
public void onTracksChanged(@NonNull TrackGroupArray trackGroups, @NonNull TrackSelectionArray trackSelections) {
|
||||||
|
Log.i(TAG, "onTracksChanged");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadingChanged(boolean isLoading) {
|
||||||
|
Log.i(TAG, "onLoadingChanged: " + isLoading);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||||
|
Log.i(TAG, "onPlayerStateChanged playWhenReady: " + playWhenReady);
|
||||||
|
Log.i(TAG, "onPlayerStateChanged playbackState: " + playbackState);
|
||||||
|
|
||||||
|
if (callbacks == null) return;
|
||||||
|
if (requestProgress != 0 && playbackState == Player.STATE_READY) {
|
||||||
|
exoPlayer.seekTo(requestProgress);
|
||||||
|
|
||||||
|
requestProgress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exoPlayer.isPlaying() || requestPlay && playbackState == ExoPlayer.STATE_READY) {
|
||||||
|
requestPlay = false;
|
||||||
|
isPlaying = true;
|
||||||
|
|
||||||
|
exoPlayer.setPlayWhenReady(true);
|
||||||
|
callbacks.onTrackStarted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionDiscontinuity(int reason) {
|
||||||
|
Log.i(TAG, "onPositionDiscontinuity: " + reason);
|
||||||
|
int windowIndex = exoPlayer.getCurrentWindowIndex();
|
||||||
|
|
||||||
|
if (windowIndex == 1) {
|
||||||
|
mediaSource.removeMediaSource(0);
|
||||||
|
if (exoPlayer.isPlaying()) {
|
||||||
|
// there are still songs left in the queue
|
||||||
|
callbacks.onTrackWentToNext();
|
||||||
|
} else {
|
||||||
|
callbacks.onTrackEnded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerError(ExoPlaybackException error) {
|
||||||
|
Log.i(TAG, "onPlayerError: " + error.getMessage());
|
||||||
|
if (context == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
|
||||||
|
exoPlayer.release();
|
||||||
|
|
||||||
|
exoPlayer = new SimpleExoPlayer.Builder(context).build();
|
||||||
|
isReady = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public MultiPlayer(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
Dispatcher dispatcher = new Dispatcher();
|
||||||
|
dispatcher.setMaxRequests(1);
|
||||||
|
|
||||||
|
httpClient = new OkHttpClient.Builder().dispatcher(dispatcher).build();
|
||||||
|
|
||||||
|
exoPlayer = new SimpleExoPlayer.Builder(context).build();
|
||||||
|
mediaSource = new ConcatenatingMediaSource();
|
||||||
|
|
||||||
|
exoPlayer.addListener(eventListener);
|
||||||
|
exoPlayer.prepare(mediaSource);
|
||||||
|
exoPlayer.setRepeatMode(Player.REPEAT_MODE_OFF);
|
||||||
|
|
||||||
|
long cacheSize = PreferenceUtil.getInstance(context).getMediaCacheSize();
|
||||||
|
LeastRecentlyUsedCacheEvictor recentlyUsedCache = new LeastRecentlyUsedCacheEvictor(cacheSize);
|
||||||
|
ExoDatabaseProvider databaseProvider = new ExoDatabaseProvider(context);
|
||||||
|
|
||||||
|
File cacheDirectory = new File(context.getCacheDir(), "exoplayer");
|
||||||
|
simpleCache = new SimpleCache(cacheDirectory, recentlyUsedCache, databaseProvider);
|
||||||
|
dataSource = buildDataSourceFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDataSource(Song song) {
|
||||||
|
isReady = false;
|
||||||
|
mediaSource = new ConcatenatingMediaSource();
|
||||||
|
|
||||||
|
exoPlayer.addListener(eventListener);
|
||||||
|
exoPlayer.prepare(mediaSource);
|
||||||
|
|
||||||
|
// queue and other information is currently handled outside exoplayer
|
||||||
|
exoPlayer.setRepeatMode(Player.REPEAT_MODE_OFF);
|
||||||
|
|
||||||
|
appendDataSource(MusicUtil.getSongFileUri(song));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void queueDataSource(Song song) {
|
||||||
|
String path = MusicUtil.getSongFileUri(song);
|
||||||
|
if (mediaSource.getSize() == 2 && mediaSource.getMediaSource(1).getTag() != path) {
|
||||||
|
mediaSource.removeMediaSource(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaSource.getSize() != 2) {
|
||||||
|
appendDataSource(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendDataSource(String path) {
|
||||||
|
Uri uri = Uri.parse(path);
|
||||||
|
|
||||||
|
httpClient.newCall(new Request.Builder().url(path).head().build()).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response) throws IOException {
|
||||||
|
MediaSource source;
|
||||||
|
if (response.header("Content-Type").equals("application/x-mpegURL")) {
|
||||||
|
source = new HlsMediaSource.Factory(dataSource)
|
||||||
|
.setTag(path)
|
||||||
|
.setAllowChunklessPreparation(true)
|
||||||
|
.createMediaSource(uri);
|
||||||
|
} else {
|
||||||
|
source = new ProgressiveMediaSource.Factory(dataSource)
|
||||||
|
.setTag(path)
|
||||||
|
.createMediaSource(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaSource.addMediaSource(source);
|
||||||
|
isReady = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataSource.Factory buildDataSourceFactory() {
|
||||||
|
return () -> new CacheDataSource(
|
||||||
|
simpleCache,
|
||||||
|
new DefaultDataSourceFactory(context, context.getPackageName(), null).createDataSource(),
|
||||||
|
new FileDataSource(),
|
||||||
|
new CacheDataSink(simpleCache, 10 * 1024 * 1024),
|
||||||
|
CacheDataSource.FLAG_BLOCK_ON_CACHE,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCallbacks(Playback.PlaybackCallbacks callbacks) {
|
||||||
|
this.callbacks = callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReady() {
|
||||||
|
return isReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return isReady && isPlaying;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
if (!isReady) {
|
||||||
|
requestPlay = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPlaying = true;
|
||||||
|
exoPlayer.setPlayWhenReady(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause() {
|
||||||
|
isPlaying = false;
|
||||||
|
exoPlayer.setPlayWhenReady(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
simpleCache.release();
|
||||||
|
exoPlayer.release();
|
||||||
|
|
||||||
|
exoPlayer = null;
|
||||||
|
isReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProgress() {
|
||||||
|
if (!isReady) return -1;
|
||||||
|
return (int) exoPlayer.getCurrentPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDuration() {
|
||||||
|
if (!isReady) return -1;
|
||||||
|
return (int) exoPlayer.getDuration();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(int progress) {
|
||||||
|
if (!isReady) {
|
||||||
|
requestProgress = progress;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exoPlayer.seekTo(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVolume(int volume) {
|
||||||
|
exoPlayer.setVolume(volume / 100f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVolume() {
|
||||||
|
return (int) (exoPlayer.getVolume() * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,181 @@
|
||||||
|
package com.cappielloantonio.play.service.notification;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.palette.graphics.Palette;
|
||||||
|
|
||||||
|
import com.bumptech.glide.request.target.CustomTarget;
|
||||||
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
|
import com.cappielloantonio.play.R;
|
||||||
|
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
||||||
|
import com.cappielloantonio.play.glide.palette.BitmapPaletteWrapper;
|
||||||
|
import com.cappielloantonio.play.model.Song;
|
||||||
|
import com.cappielloantonio.play.service.MusicService;
|
||||||
|
import com.cappielloantonio.play.ui.activities.MainActivity;
|
||||||
|
|
||||||
|
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||||
|
import static com.cappielloantonio.play.service.MusicService.ACTION_REWIND;
|
||||||
|
import static com.cappielloantonio.play.service.MusicService.ACTION_SKIP;
|
||||||
|
import static com.cappielloantonio.play.service.MusicService.ACTION_TOGGLE;
|
||||||
|
|
||||||
|
public class PlayingNotification {
|
||||||
|
|
||||||
|
private static final int NOTIFICATION_ID = 1;
|
||||||
|
static final String NOTIFICATION_CHANNEL_ID = "playing_notification";
|
||||||
|
|
||||||
|
private static final int NOTIFY_MODE_FOREGROUND = 1;
|
||||||
|
private static final int NOTIFY_MODE_BACKGROUND = 0;
|
||||||
|
|
||||||
|
private int notifyMode = NOTIFY_MODE_BACKGROUND;
|
||||||
|
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
protected MusicService service;
|
||||||
|
boolean stopped;
|
||||||
|
|
||||||
|
public synchronized void init(MusicService service) {
|
||||||
|
this.service = service;
|
||||||
|
notificationManager = (NotificationManager) service.getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
createNotificationChannel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void update() {
|
||||||
|
stopped = false;
|
||||||
|
|
||||||
|
final Song song = service.getCurrentSong();
|
||||||
|
|
||||||
|
final boolean isPlaying = service.isPlaying();
|
||||||
|
|
||||||
|
final int playButtonResId = isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp;
|
||||||
|
|
||||||
|
Intent action = new Intent(service, MainActivity.class);
|
||||||
|
action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
final PendingIntent clickIntent = PendingIntent.getActivity(service, 0, action, 0);
|
||||||
|
|
||||||
|
final ComponentName serviceName = new ComponentName(service, MusicService.class);
|
||||||
|
Intent intent = new Intent(MusicService.ACTION_QUIT);
|
||||||
|
intent.setComponent(serviceName);
|
||||||
|
final PendingIntent deleteIntent = PendingIntent.getService(service, 0, intent, 0);
|
||||||
|
|
||||||
|
final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size);
|
||||||
|
service.runOnUiThread(() -> CustomGlideRequest.Builder
|
||||||
|
.from(service, song.getPrimary(), song.getBlurHash(), CustomGlideRequest.PRIMARY, CustomGlideRequest.TOP_QUALITY)
|
||||||
|
.build()
|
||||||
|
.into(new CustomTarget<BitmapPaletteWrapper>(bigNotificationImageSize, bigNotificationImageSize) {
|
||||||
|
@Override
|
||||||
|
public void onResourceReady(@NonNull BitmapPaletteWrapper resource, Transition<? super BitmapPaletteWrapper> glideAnimation) {
|
||||||
|
Palette palette = resource.getPalette();
|
||||||
|
update(resource.getBitmap(), palette.getVibrantColor(palette.getMutedColor(Color.TRANSPARENT)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFailed(Drawable drawable) {
|
||||||
|
update(null, Color.TRANSPARENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadCleared(Drawable drawable) {
|
||||||
|
update(null, Color.TRANSPARENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(Bitmap bitmap, int color) {
|
||||||
|
if (bitmap == null)
|
||||||
|
bitmap = BitmapFactory.decodeResource(service.getResources(), R.drawable.default_album_art);
|
||||||
|
NotificationCompat.Action playPauseAction = new NotificationCompat.Action(playButtonResId,
|
||||||
|
service.getString(R.string.action_play_pause),
|
||||||
|
retrievePlaybackAction(ACTION_TOGGLE));
|
||||||
|
NotificationCompat.Action previousAction = new NotificationCompat.Action(R.drawable.ic_skip_previous_white_24dp,
|
||||||
|
service.getString(R.string.action_previous),
|
||||||
|
retrievePlaybackAction(ACTION_REWIND));
|
||||||
|
NotificationCompat.Action nextAction = new NotificationCompat.Action(R.drawable.ic_skip_next_white_24dp,
|
||||||
|
service.getString(R.string.action_next),
|
||||||
|
retrievePlaybackAction(ACTION_SKIP));
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID)
|
||||||
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
|
.setSubText(song.getAlbumName())
|
||||||
|
.setLargeIcon(bitmap)
|
||||||
|
.setContentIntent(clickIntent)
|
||||||
|
.setDeleteIntent(deleteIntent)
|
||||||
|
.setContentTitle(song.getTitle())
|
||||||
|
.setContentText(song.getArtistName())
|
||||||
|
.setOngoing(isPlaying)
|
||||||
|
.setShowWhen(false)
|
||||||
|
.addAction(previousAction)
|
||||||
|
.addAction(playPauseAction)
|
||||||
|
.addAction(nextAction);
|
||||||
|
|
||||||
|
builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle().setMediaSession(service.getMediaSession().getSessionToken())
|
||||||
|
.setShowActionsInCompactView(0, 1, 2))
|
||||||
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
.setColor(color);
|
||||||
|
|
||||||
|
// notification has been stopped before loading was finished
|
||||||
|
if (stopped) return;
|
||||||
|
|
||||||
|
updateNotifyModeAndPostNotification(builder.build());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void stop() {
|
||||||
|
stopped = true;
|
||||||
|
service.stopForeground(true);
|
||||||
|
notificationManager.cancel(NOTIFICATION_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PendingIntent retrievePlaybackAction(final String action) {
|
||||||
|
final ComponentName serviceName = new ComponentName(service, MusicService.class);
|
||||||
|
Intent intent = new Intent(action);
|
||||||
|
intent.setComponent(serviceName);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
return PendingIntent.getService(service, 0, intent, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateNotifyModeAndPostNotification(Notification notification) {
|
||||||
|
int newNotifyMode;
|
||||||
|
if (service.isPlaying()) {
|
||||||
|
newNotifyMode = NOTIFY_MODE_FOREGROUND;
|
||||||
|
} else {
|
||||||
|
newNotifyMode = NOTIFY_MODE_BACKGROUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notifyMode != newNotifyMode && newNotifyMode == NOTIFY_MODE_BACKGROUND) {
|
||||||
|
service.stopForeground(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newNotifyMode == NOTIFY_MODE_FOREGROUND) {
|
||||||
|
service.startForeground(NOTIFICATION_ID, notification);
|
||||||
|
} else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyMode = newNotifyMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(26)
|
||||||
|
private void createNotificationChannel() {
|
||||||
|
NotificationChannel notificationChannel = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID);
|
||||||
|
if (notificationChannel == null) {
|
||||||
|
notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, service.getString(R.string.playing_notification_name), NotificationManager.IMPORTANCE_LOW);
|
||||||
|
notificationChannel.setDescription(service.getString(R.string.playing_notification_description));
|
||||||
|
notificationChannel.enableLights(false);
|
||||||
|
notificationChannel.enableVibration(false);
|
||||||
|
|
||||||
|
notificationManager.createNotificationChannel(notificationChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.cappielloantonio.play.service.playback;
|
||||||
|
|
||||||
|
import com.cappielloantonio.play.model.Song;
|
||||||
|
|
||||||
|
public interface Playback {
|
||||||
|
void setDataSource(Song song);
|
||||||
|
|
||||||
|
void queueDataSource(Song song);
|
||||||
|
|
||||||
|
void setCallbacks(PlaybackCallbacks callbacks);
|
||||||
|
|
||||||
|
boolean isReady();
|
||||||
|
|
||||||
|
boolean isPlaying();
|
||||||
|
|
||||||
|
void start();
|
||||||
|
|
||||||
|
void pause();
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
int getProgress();
|
||||||
|
|
||||||
|
int getDuration();
|
||||||
|
|
||||||
|
void setProgress(int progress);
|
||||||
|
|
||||||
|
void setVolume(int volume);
|
||||||
|
|
||||||
|
int getVolume();
|
||||||
|
|
||||||
|
interface PlaybackCallbacks {
|
||||||
|
void onTrackStarted();
|
||||||
|
|
||||||
|
void onTrackWentToNext();
|
||||||
|
|
||||||
|
void onTrackEnded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -135,7 +135,7 @@ public class MainActivity extends BaseActivity {
|
||||||
* lo chiudo
|
* lo chiudo
|
||||||
*/
|
*/
|
||||||
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
|
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
|
||||||
if(bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED && (
|
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED && (
|
||||||
destination.getId() == R.id.homeFragment ||
|
destination.getId() == R.id.homeFragment ||
|
||||||
destination.getId() == R.id.libraryFragment ||
|
destination.getId() == R.id.libraryFragment ||
|
||||||
destination.getId() == R.id.searchFragment ||
|
destination.getId() == R.id.searchFragment ||
|
||||||
|
|
@ -164,32 +164,32 @@ public class MainActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback =
|
private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback =
|
||||||
new BottomSheetBehavior.BottomSheetCallback() {
|
new BottomSheetBehavior.BottomSheetCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onStateChanged(@NonNull View view, int state) {
|
public void onStateChanged(@NonNull View view, int state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case BottomSheetBehavior.STATE_SETTLING:
|
case BottomSheetBehavior.STATE_SETTLING:
|
||||||
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
||||||
if(playerBottomSheetFragment == null) break;
|
if (playerBottomSheetFragment == null) break;
|
||||||
|
|
||||||
playerBottomSheetFragment.scrollOnTop();
|
playerBottomSheetFragment.scrollOnTop();
|
||||||
break;
|
break;
|
||||||
case BottomSheetBehavior.STATE_HIDDEN:
|
case BottomSheetBehavior.STATE_HIDDEN:
|
||||||
mainViewModel.deleteQueue();
|
mainViewModel.deleteQueue();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSlide(@NonNull View view, float slideOffset) {
|
public void onSlide(@NonNull View view, float slideOffset) {
|
||||||
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
||||||
if(playerBottomSheetFragment == null) return;
|
if (playerBottomSheetFragment == null) return;
|
||||||
|
|
||||||
float condensedSlideOffset = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f;
|
float condensedSlideOffset = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f;
|
||||||
playerBottomSheetFragment.getPlayerHeader().setAlpha(1 - condensedSlideOffset);
|
playerBottomSheetFragment.getPlayerHeader().setAlpha(1 - condensedSlideOffset);
|
||||||
playerBottomSheetFragment.getPlayerHeader().setVisibility(condensedSlideOffset > 0.99 ? View.GONE : View.VISIBLE);
|
playerBottomSheetFragment.getPlayerHeader().setVisibility(condensedSlideOffset > 0.99 ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Scroll on top del bottom sheet quando chiudo
|
* Scroll on top del bottom sheet quando chiudo
|
||||||
|
|
@ -197,7 +197,7 @@ public class MainActivity extends BaseActivity {
|
||||||
*/
|
*/
|
||||||
public void setBottomSheetMusicInfo(Song song) {
|
public void setBottomSheetMusicInfo(Song song) {
|
||||||
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
||||||
if(playerBottomSheetFragment == null) return;
|
if (playerBottomSheetFragment == null) return;
|
||||||
|
|
||||||
playerBottomSheetFragment.scrollPager(song, 0, false);
|
playerBottomSheetFragment.scrollPager(song, 0, false);
|
||||||
}
|
}
|
||||||
|
|
@ -246,7 +246,7 @@ public class MainActivity extends BaseActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if(bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED)
|
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED)
|
||||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||||
else
|
else
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
@ -20,7 +19,6 @@ import com.cappielloantonio.play.adapter.PlayerSongQueueAdapter;
|
||||||
import com.cappielloantonio.play.databinding.FragmentPlayerBottomSheetBinding;
|
import com.cappielloantonio.play.databinding.FragmentPlayerBottomSheetBinding;
|
||||||
import com.cappielloantonio.play.model.Song;
|
import com.cappielloantonio.play.model.Song;
|
||||||
import com.cappielloantonio.play.ui.activities.MainActivity;
|
import com.cappielloantonio.play.ui.activities.MainActivity;
|
||||||
import com.cappielloantonio.play.util.MusicUtil;
|
|
||||||
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
|
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
|
||||||
|
|
||||||
public class PlayerBottomSheetFragment extends Fragment {
|
public class PlayerBottomSheetFragment extends Fragment {
|
||||||
|
|
@ -98,7 +96,7 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playSong(Song song) {
|
private void playSong(Song song) {
|
||||||
Toast.makeText(activity, MusicUtil.getSongFileUri(song), Toast.LENGTH_SHORT).show();
|
// Toast.makeText(activity, MusicUtil.getSongFileUri(song), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public View getPlayerHeader() {
|
public View getPlayerHeader() {
|
||||||
|
|
|
||||||
|
|
@ -19,18 +19,24 @@ public class PreferenceUtil {
|
||||||
public static final String TOKEN = "token";
|
public static final String TOKEN = "token";
|
||||||
public static final String MUSIC_LIBRARY_ID = "music_library_id";
|
public static final String MUSIC_LIBRARY_ID = "music_library_id";
|
||||||
|
|
||||||
|
public static final String SHUFFLE = "shuffle";
|
||||||
|
public static final String REPEAT = "repeat";
|
||||||
|
public static final String POSITION = "position";
|
||||||
|
public static final String PROGRESS = "progress";
|
||||||
|
|
||||||
public static final String SYNC = "sync";
|
public static final String SYNC = "sync";
|
||||||
public static final String SONG_GENRE_SYNC = "song_genre_sync";
|
public static final String SONG_GENRE_SYNC = "song_genre_sync";
|
||||||
|
|
||||||
public static final String HOST_URL = "host";
|
public static final String HOST_URL = "host";
|
||||||
public static final String IMAGE_CACHE_SIZE = "image_cache_size";
|
public static final String IMAGE_CACHE_SIZE = "image_cache_size";
|
||||||
|
public static final String MEDIA_CACHE_SIZE = "media_cache_size";
|
||||||
|
|
||||||
public static final String TRANSCODE_CODEC = "transcode_codec";
|
public static final String TRANSCODE_CODEC = "transcode_codec";
|
||||||
public static final String DIRECT_PLAY_CODECS = "direct_play_codecs";
|
public static final String DIRECT_PLAY_CODECS = "direct_play_codecs";
|
||||||
public static final String MAXIMUM_BITRATE = "maximum_bitrate";
|
public static final String MAXIMUM_BITRATE = "maximum_bitrate";
|
||||||
|
public static final String AUDIO_DUCKING = "audio_ducking";
|
||||||
|
|
||||||
private static PreferenceUtil sInstance;
|
private static PreferenceUtil sInstance;
|
||||||
|
|
||||||
private final SharedPreferences mPreferences;
|
private final SharedPreferences mPreferences;
|
||||||
|
|
||||||
private PreferenceUtil(final Context context) {
|
private PreferenceUtil(final Context context) {
|
||||||
|
|
@ -156,4 +162,21 @@ public class PreferenceUtil {
|
||||||
editor.putStringSet(DIRECT_PLAY_CODECS, codecNames);
|
editor.putStringSet(DIRECT_PLAY_CODECS, codecNames);
|
||||||
editor.apply();
|
editor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final int getMediaCacheSize() {
|
||||||
|
return Integer.parseInt(mPreferences.getString(MEDIA_CACHE_SIZE, "400000000"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean getAudioDucking() {
|
||||||
|
return mPreferences.getBoolean(AUDIO_DUCKING, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void registerOnSharedPreferenceChangedListener(SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener) {
|
||||||
|
mPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterOnSharedPreferenceChangedListener(SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener) {
|
||||||
|
mPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
BIN
app/src/main/res/drawable/ic_notification.png
Normal file
BIN
app/src/main/res/drawable/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
9
app/src/main/res/drawable/ic_pause_white_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_pause_white_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" />
|
||||||
|
</vector>
|
||||||
9
app/src/main/res/drawable/ic_play_arrow_white_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_play_arrow_white_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M8,5v14l11,-7z" />
|
||||||
|
</vector>
|
||||||
9
app/src/main/res/drawable/ic_skip_next_white_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_skip_next_white_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -12,4 +12,7 @@
|
||||||
<dimen name="bottom_sheet_peek_height">56dp</dimen>
|
<dimen name="bottom_sheet_peek_height">56dp</dimen>
|
||||||
|
|
||||||
<dimen name="global_padding_bottom">64dp</dimen>
|
<dimen name="global_padding_bottom">64dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="notification_big_image_size">128dp</dimen>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -29,7 +29,22 @@
|
||||||
|
|
||||||
<string name="disable">Disable</string>
|
<string name="disable">Disable</string>
|
||||||
<string name="ignore">Ignore</string>
|
<string name="ignore">Ignore</string>
|
||||||
<!-- TODO: Remove or change this placeholder text -->
|
|
||||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
|
||||||
|
|
||||||
|
<string name="playback_channel_name">Playback</string>
|
||||||
|
<string name="download_channel_name">Download</string>
|
||||||
|
<string name="unplayable_file">Couldn\'t play this song.</string>
|
||||||
|
<string name="playlist_is_empty">Playlist is empty</string>
|
||||||
|
<string name="audio_focus_denied">Audio focus denied.</string>
|
||||||
|
|
||||||
|
<string name="action_play_next">Play next</string>
|
||||||
|
<string name="action_play">Play</string>
|
||||||
|
<string name="action_play_pause">Play/Pause</string>
|
||||||
|
<string name="action_previous">Previous</string>
|
||||||
|
<string name="action_next">Next</string>
|
||||||
|
<string name="action_add_to_queue">Add to queue</string>
|
||||||
|
<string name="action_remove_from_queue">Remove from queue</string>
|
||||||
|
<string name="action_add_to_playlist">Add to playlist</string>
|
||||||
|
|
||||||
|
<string name="playing_notification_description">The playing notification provides actions for play/pause etc.</string>
|
||||||
|
<string name="playing_notification_name">Playing Notification</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue