mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
Preparation to music streaming
This commit is contained in:
parent
f837bb14e2
commit
a28ad27288
23 changed files with 615 additions and 279 deletions
|
|
@ -30,6 +30,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
|
dataBinding true
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +76,9 @@ dependencies {
|
||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
implementation "com.github.woltapp:blurhash:f41a23cc50"
|
implementation "com.github.woltapp:blurhash:f41a23cc50"
|
||||||
|
|
||||||
|
// Exoplayer
|
||||||
|
implementation 'com.google.android.exoplayer:exoplayer:2.12.2'
|
||||||
|
|
||||||
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"
|
||||||
testImplementation 'junit:junit:4.13.1'
|
testImplementation 'junit:junit:4.13.1'
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,14 @@ public class PlayerNowPlayingSongAdapter extends RecyclerView.Adapter<PlayerNowP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Song getItem(int position) {
|
||||||
|
try {
|
||||||
|
return songs.get(position);
|
||||||
|
} catch ( IndexOutOfBoundsException e ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setItems(List<Song> songs) {
|
public void setItems(List<Song> songs) {
|
||||||
this.songs = songs;
|
this.songs = songs;
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,14 @@ import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.cappielloantonio.play.App;
|
import com.cappielloantonio.play.App;
|
||||||
import com.cappielloantonio.play.R;
|
import com.cappielloantonio.play.R;
|
||||||
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
||||||
import com.cappielloantonio.play.model.Song;
|
import com.cappielloantonio.play.model.Song;
|
||||||
import com.cappielloantonio.play.repository.QueueRepository;
|
|
||||||
import com.cappielloantonio.play.repository.SongRepository;
|
import com.cappielloantonio.play.repository.SongRepository;
|
||||||
import com.cappielloantonio.play.ui.activities.MainActivity;
|
import com.cappielloantonio.play.ui.fragment.PlayerBottomSheetFragment;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -29,14 +27,12 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||||
|
|
||||||
private List<Song> songs;
|
private List<Song> songs;
|
||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
private MainActivity mainActivity;
|
private PlayerBottomSheetFragment playerBottomSheetFragment;
|
||||||
private Context context;
|
private Context context;
|
||||||
private FragmentManager fragmentManager;
|
|
||||||
|
|
||||||
public PlayerSongQueueAdapter(MainActivity mainActivity, Context context, FragmentManager fragmentManager) {
|
public PlayerSongQueueAdapter(Context context, PlayerBottomSheetFragment playerBottomSheetFragment) {
|
||||||
this.mainActivity = mainActivity;
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.fragmentManager = fragmentManager;
|
this.playerBottomSheetFragment = playerBottomSheetFragment;
|
||||||
this.mInflater = LayoutInflater.from(context);
|
this.mInflater = LayoutInflater.from(context);
|
||||||
this.songs = new ArrayList<>();
|
this.songs = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
@ -83,9 +79,9 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
SongRepository songRepository = new SongRepository(App.getInstance());
|
SongRepository songRepository = new SongRepository(App.getInstance());
|
||||||
QueueRepository queueRepository = new QueueRepository(App.getInstance());
|
|
||||||
|
|
||||||
songRepository.increasePlayCount(songs.get(getAdapterPosition()));
|
songRepository.increasePlayCount(songs.get(getAdapterPosition()));
|
||||||
|
|
||||||
|
playerBottomSheetFragment.scrollPager(songs.get(getAdapterPosition()), getAdapterPosition(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.navigation.Navigation;
|
import androidx.navigation.Navigation;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
|
@ -19,6 +20,7 @@ import com.cappielloantonio.play.model.Song;
|
||||||
import com.cappielloantonio.play.repository.QueueRepository;
|
import com.cappielloantonio.play.repository.QueueRepository;
|
||||||
import com.cappielloantonio.play.repository.SongRepository;
|
import com.cappielloantonio.play.repository.SongRepository;
|
||||||
import com.cappielloantonio.play.ui.activities.MainActivity;
|
import com.cappielloantonio.play.ui.activities.MainActivity;
|
||||||
|
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -92,6 +94,10 @@ public class RecentMusicAdapter extends RecyclerView.Adapter<RecentMusicAdapter.
|
||||||
queueRepository.insertAllAndStartNew(songs.subList(getAdapterPosition(), songs.size()));
|
queueRepository.insertAllAndStartNew(songs.subList(getAdapterPosition(), songs.size()));
|
||||||
|
|
||||||
mainActivity.isBottomSheetInPeek(true);
|
mainActivity.isBottomSheetInPeek(true);
|
||||||
|
mainActivity.setBottomSheetMusicInfo(songs.get(getAdapterPosition()));
|
||||||
|
|
||||||
|
PlayerBottomSheetViewModel playerBottomSheetViewModel = new ViewModelProvider(mainActivity).get(PlayerBottomSheetViewModel.class);
|
||||||
|
playerBottomSheetViewModel.setNowPlayingSong(songs.get(getAdapterPosition()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.navigation.Navigation;
|
import androidx.navigation.Navigation;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
|
@ -20,6 +21,7 @@ import com.cappielloantonio.play.repository.QueueRepository;
|
||||||
import com.cappielloantonio.play.repository.SongRepository;
|
import com.cappielloantonio.play.repository.SongRepository;
|
||||||
import com.cappielloantonio.play.ui.activities.MainActivity;
|
import com.cappielloantonio.play.ui.activities.MainActivity;
|
||||||
import com.cappielloantonio.play.util.Util;
|
import com.cappielloantonio.play.util.Util;
|
||||||
|
import com.cappielloantonio.play.viewmodel.PlayerBottomSheetViewModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -96,6 +98,10 @@ public class SongResultSearchAdapter extends RecyclerView.Adapter<SongResultSear
|
||||||
queueRepository.insertAllAndStartNew(songs.subList(getAdapterPosition(), songs.size()));
|
queueRepository.insertAllAndStartNew(songs.subList(getAdapterPosition(), songs.size()));
|
||||||
|
|
||||||
mainActivity.isBottomSheetInPeek(true);
|
mainActivity.isBottomSheetInPeek(true);
|
||||||
|
mainActivity.setBottomSheetMusicInfo(songs.get(getAdapterPosition()));
|
||||||
|
|
||||||
|
PlayerBottomSheetViewModel playerBottomSheetViewModel = new ViewModelProvider(mainActivity).get(PlayerBottomSheetViewModel.class);
|
||||||
|
playerBottomSheetViewModel.setNowPlayingSong(songs.get(getAdapterPosition()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,24 @@ import java.util.List;
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public interface QueueDao {
|
public interface QueueDao {
|
||||||
@Query("SELECT * FROM song JOIN queue ON song.id = queue.song_id")
|
@Query("SELECT * FROM song JOIN queue ON song.id = queue.id")
|
||||||
LiveData<List<Song>> getAll();
|
LiveData<List<Song>> getAll();
|
||||||
|
|
||||||
@Query("SELECT * FROM song JOIN queue ON song.id = queue.song_id")
|
@Query("SELECT * FROM song JOIN queue ON song.id = queue.id")
|
||||||
List<Song> getAllSimple();
|
List<Song> getAllSimple();
|
||||||
|
|
||||||
|
@Query("SELECT * FROM song JOIN queue ON song.id = queue.id WHERE queue.rowid = :position")
|
||||||
|
Song getSongByIndex(int position);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM song JOIN queue ON song.id = queue.id WHERE queue.last_played != 0 ORDER BY queue.last_played DESC LIMIT 1")
|
||||||
|
LiveData<Song> getLastPlayedSong();
|
||||||
|
|
||||||
|
@Query("UPDATE queue SET last_played = :timestamp WHERE queue.rowid = :position")
|
||||||
|
void setLastPlayedSong(int position, long timestamp);
|
||||||
|
|
||||||
|
@Query("UPDATE queue SET last_played = :timestamp WHERE queue.id = :songID")
|
||||||
|
void setLastPlayedSong(String songID, long timestamp);
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
void insert(Queue songQueueObject);
|
void insert(Queue songQueueObject);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,15 @@ public interface SongDao {
|
||||||
@Query("SELECT * FROM song INNER Join song_genre_cross ON song.id = song_genre_cross.song_id AND song_genre_cross.genre_id IN (:filters) GROUP BY song.id")
|
@Query("SELECT * FROM song INNER Join song_genre_cross ON song.id = song_genre_cross.song_id AND song_genre_cross.genre_id IN (:filters) GROUP BY song.id")
|
||||||
LiveData<List<Song>> getFilteredSong(ArrayList<String> filters);
|
LiveData<List<Song>> getFilteredSong(ArrayList<String> filters);
|
||||||
|
|
||||||
@Query("SELECT * FROM song WHERE favorite = 1 ORDER BY play_count DESC LIMIT :number")
|
@Query("SELECT * FROM song WHERE favorite = 1 LIMIT :number")
|
||||||
LiveData<List<Song>> getFavoriteSongSample(int number);
|
LiveData<List<Song>> getFavoriteSongSample(int number);
|
||||||
|
|
||||||
@Query("SELECT * FROM song WHERE favorite = 1 ORDER BY play_count DESC")
|
@Query("SELECT * FROM song WHERE favorite = 1")
|
||||||
LiveData<List<Song>> getFavoriteSong();
|
LiveData<List<Song>> getFavoriteSong();
|
||||||
|
|
||||||
|
@Query("SELECT * FROM song WHERE id = :id")
|
||||||
|
Song getSongByID(String id);
|
||||||
|
|
||||||
@Query("SELECT EXISTS(SELECT * FROM song WHERE id = :id)")
|
@Query("SELECT EXISTS(SELECT * FROM song WHERE id = :id)")
|
||||||
boolean exist(String id);
|
boolean exist(String id);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ public class CustomGlideRequest {
|
||||||
public static String createUrl(String item, String itemType, String quality) {
|
public static String createUrl(String item, String itemType, String quality) {
|
||||||
ImageOptions options = new ImageOptions();
|
ImageOptions options = new ImageOptions();
|
||||||
|
|
||||||
switch(itemType) {
|
switch (itemType) {
|
||||||
case PRIMARY: {
|
case PRIMARY: {
|
||||||
options.setImageType(ImageType.Primary);
|
options.setImageType(ImageType.Primary);
|
||||||
break;
|
break;
|
||||||
|
|
@ -85,7 +85,7 @@ public class CustomGlideRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(quality) {
|
switch (quality) {
|
||||||
case TOP_QUALITY: {
|
case TOP_QUALITY: {
|
||||||
options.setQuality(100);
|
options.setQuality(100);
|
||||||
options.setMaxHeight(800);
|
options.setMaxHeight(800);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.cappielloantonio.play.model;
|
||||||
|
|
||||||
|
public class DirectPlayCodec {
|
||||||
|
public Codec codec;
|
||||||
|
public boolean selected;
|
||||||
|
|
||||||
|
public DirectPlayCodec(Codec codec, boolean selected) {
|
||||||
|
this.codec = codec;
|
||||||
|
this.selected = selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Codec {
|
||||||
|
FLAC("FLAC", "FLAC", "flac|flac"),
|
||||||
|
MP3("MP3", "MP3", "mp3|mp3"),
|
||||||
|
OPUS("Opus", "Opus", "opus|opus"),
|
||||||
|
AAC("M4A", "AAC", "m4a|aac"),
|
||||||
|
VORBIS("OGG", "Vorbis", "ogg|vorbis"),
|
||||||
|
OGG("OGG", "Opus", "ogg|opus"),
|
||||||
|
MKA("MKA", "Opus", "mka|opus");
|
||||||
|
|
||||||
|
public final String container;
|
||||||
|
public final String codec;
|
||||||
|
public final String value;
|
||||||
|
|
||||||
|
Codec(String container, String codec, String value) {
|
||||||
|
this.container = container;
|
||||||
|
this.codec = codec;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,35 +3,21 @@ package com.cappielloantonio.play.model;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.room.ColumnInfo;
|
import androidx.room.ColumnInfo;
|
||||||
import androidx.room.Entity;
|
import androidx.room.Entity;
|
||||||
import androidx.room.Ignore;
|
|
||||||
import androidx.room.PrimaryKey;
|
import androidx.room.PrimaryKey;
|
||||||
|
|
||||||
@Entity(tableName = "queue")
|
@Entity(tableName = "queue")
|
||||||
public class Queue {
|
public class Queue {
|
||||||
@NonNull
|
@NonNull
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey
|
||||||
@ColumnInfo(name = "id")
|
@ColumnInfo(name = "id")
|
||||||
private int id;
|
|
||||||
|
|
||||||
@ColumnInfo(name = "song_id")
|
|
||||||
private String songID;
|
private String songID;
|
||||||
|
|
||||||
public Queue(@NonNull int id, String songID) {
|
@ColumnInfo(name = "last_played")
|
||||||
this.id = id;
|
private long lastPlayed;
|
||||||
|
|
||||||
|
public Queue(String songID, long lastPlayed) {
|
||||||
this.songID = songID;
|
this.songID = songID;
|
||||||
}
|
this.lastPlayed = lastPlayed;
|
||||||
|
|
||||||
@Ignore
|
|
||||||
public Queue(String songID) {
|
|
||||||
this.songID = songID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(int id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSongID() {
|
public String getSongID() {
|
||||||
|
|
@ -41,4 +27,12 @@ public class Queue {
|
||||||
public void setSongID(String songID) {
|
public void setSongID(String songID) {
|
||||||
this.songID = songID;
|
this.songID = songID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getLastPlayed() {
|
||||||
|
return lastPlayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastPlayed(long lastPlayed) {
|
||||||
|
this.lastPlayed = lastPlayed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import com.cappielloantonio.play.model.Queue;
|
||||||
import com.cappielloantonio.play.model.Song;
|
import com.cappielloantonio.play.model.Song;
|
||||||
import com.cappielloantonio.play.util.QueueUtil;
|
import com.cappielloantonio.play.util.QueueUtil;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -18,6 +19,7 @@ public class QueueRepository {
|
||||||
|
|
||||||
private QueueDao queueDao;
|
private QueueDao queueDao;
|
||||||
private LiveData<List<Song>> listLiveQueue;
|
private LiveData<List<Song>> listLiveQueue;
|
||||||
|
private LiveData<Song> liveLastPlayedSong;
|
||||||
|
|
||||||
public QueueRepository(Application application) {
|
public QueueRepository(Application application) {
|
||||||
AppDatabase database = AppDatabase.getInstance(application);
|
AppDatabase database = AppDatabase.getInstance(application);
|
||||||
|
|
@ -29,6 +31,34 @@ public class QueueRepository {
|
||||||
return listLiveQueue;
|
return listLiveQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Song getSongByPosition(int position) {
|
||||||
|
Song song = null;
|
||||||
|
|
||||||
|
GetSongByPositionThreadSafe getSong = new GetSongByPositionThreadSafe(queueDao, position);
|
||||||
|
Thread thread = new Thread(getSong);
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
song = getSong.getSong();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return song;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Song> getLiveLastPlayedSong() {
|
||||||
|
liveLastPlayedSong = queueDao.getLastPlayedSong();
|
||||||
|
return liveLastPlayedSong;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLiveLastPlayedSong(Song song, int position) {
|
||||||
|
SetLastPlayedSongThreadSafe update = new SetLastPlayedSongThreadSafe(queueDao, song, position);
|
||||||
|
Thread thread = new Thread(update);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
public List<Song> getSongs() {
|
public List<Song> getSongs() {
|
||||||
List<Song> songs = new ArrayList<>();
|
List<Song> songs = new ArrayList<>();
|
||||||
|
|
||||||
|
|
@ -194,4 +224,44 @@ public class QueueRepository {
|
||||||
return songs;
|
return songs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class SetLastPlayedSongThreadSafe implements Runnable {
|
||||||
|
private QueueDao queueDao;
|
||||||
|
private Song song;
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
public SetLastPlayedSongThreadSafe(QueueDao queueDao, Song song, int position) {
|
||||||
|
this.queueDao = queueDao;
|
||||||
|
this.song = song;
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if(song != null)
|
||||||
|
queueDao.setLastPlayedSong(song.getId(), Instant.now().toEpochMilli());
|
||||||
|
else
|
||||||
|
queueDao.setLastPlayedSong(position, Instant.now().toEpochMilli());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GetSongByPositionThreadSafe implements Runnable {
|
||||||
|
private QueueDao queueDao;
|
||||||
|
private int position;
|
||||||
|
private Song song;
|
||||||
|
|
||||||
|
public GetSongByPositionThreadSafe(QueueDao queueDao, int position) {
|
||||||
|
this.queueDao = queueDao;
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
song = queueDao.getSongByIndex(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Song getSong() {
|
||||||
|
return song;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,23 @@ public class SongRepository {
|
||||||
return sample;
|
return sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Song getSongByID(String id) {
|
||||||
|
Song song = null;
|
||||||
|
|
||||||
|
GetSongByIDThreadSafe songByID = new GetSongByIDThreadSafe(songDao, id);
|
||||||
|
Thread thread = new Thread(songByID);
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
song = songByID.getSong();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return song;
|
||||||
|
}
|
||||||
|
|
||||||
private static class ExistThreadSafe implements Runnable {
|
private static class ExistThreadSafe implements Runnable {
|
||||||
private SongDao songDao;
|
private SongDao songDao;
|
||||||
private Song song;
|
private Song song;
|
||||||
|
|
@ -459,4 +476,24 @@ public class SongRepository {
|
||||||
return decades;
|
return decades;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class GetSongByIDThreadSafe implements Runnable {
|
||||||
|
private SongDao songDao;
|
||||||
|
private String id;
|
||||||
|
private Song song;
|
||||||
|
|
||||||
|
public GetSongByIDThreadSafe(SongDao songDao, String id) {
|
||||||
|
this.songDao = songDao;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
song = songDao.getSongByID(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Song getSong() {
|
||||||
|
return song;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.cappielloantonio.play.service;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
import com.cappielloantonio.play.R;
|
||||||
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
|
public class MusicService extends Service implements AudioManager.OnAudioFocusChangeListener {
|
||||||
|
private AudioManager audioManager;
|
||||||
|
private MediaSource mediaSource;
|
||||||
|
private SimpleExoPlayer player;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
audioManager = (AudioManager) getApplicationContext().getSystemService(AUDIO_SERVICE);
|
||||||
|
super.onCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
String input = intent.getStringExtra("playStop");
|
||||||
|
if (input != null && input.equals("play")) {
|
||||||
|
if (requestFocus()) {
|
||||||
|
initPlayer();
|
||||||
|
play();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
startForeground(1, getNotification());
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
stop();
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Notification getNotification() {
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
|
||||||
|
.setContentText("Running ...")
|
||||||
|
.setContentTitle("Play")
|
||||||
|
.setOngoing(true)
|
||||||
|
.setSmallIcon(R.drawable.exo_controls_shuffle_on)
|
||||||
|
.setChannelId("PlayApp");
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioFocusChange(int focusChange) {
|
||||||
|
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS) {
|
||||||
|
stop();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean requestFocus() {
|
||||||
|
return (audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void play() {
|
||||||
|
player.setForegroundMode(true);
|
||||||
|
player.prepare(mediaSource);
|
||||||
|
player.setPlayWhenReady(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stop() {
|
||||||
|
player.setPlayWhenReady(false);
|
||||||
|
player.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initPlayer() {
|
||||||
|
player = ExoPlayerFactory.newSimpleInstance(getApplicationContext());
|
||||||
|
DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(getApplicationContext(), "exoPlayerSample"));
|
||||||
|
mediaSource = new ProgressiveMediaSource.Factory(defaultDataSourceFactory).createMediaSource(Uri.parse("http://192.168.1.81:8096/Audio/5656e9fd11e38ba95ba4871bc061991a/universal?UserId=34addd030b4545e5ac4300dc322c9f73&DeviceId=e40853e4e7ab76f1&MaxStreamingBitrate=10000000&Container=flac|flac,mp3|mp3,opus|opus,m4a|aac,ogg|vorbis,ogg|opus,mka|opus&TranscodingContainer=ts&TranscodingProtocol=hls&AudioCodec=aac&api_key=7e6626ca220d4b01961022e148868d41"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ package com.cappielloantonio.play.ui.activities;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
@ -17,6 +16,7 @@ import com.cappielloantonio.play.App;
|
||||||
import com.cappielloantonio.play.R;
|
import com.cappielloantonio.play.R;
|
||||||
import com.cappielloantonio.play.broadcast.receiver.ConnectivityStatusBroadcastReceiver;
|
import com.cappielloantonio.play.broadcast.receiver.ConnectivityStatusBroadcastReceiver;
|
||||||
import com.cappielloantonio.play.databinding.ActivityMainBinding;
|
import com.cappielloantonio.play.databinding.ActivityMainBinding;
|
||||||
|
import com.cappielloantonio.play.model.Song;
|
||||||
import com.cappielloantonio.play.ui.activities.base.BaseActivity;
|
import com.cappielloantonio.play.ui.activities.base.BaseActivity;
|
||||||
import com.cappielloantonio.play.ui.fragment.PlayerBottomSheetFragment;
|
import com.cappielloantonio.play.ui.fragment.PlayerBottomSheetFragment;
|
||||||
import com.cappielloantonio.play.util.PreferenceUtil;
|
import com.cappielloantonio.play.util.PreferenceUtil;
|
||||||
|
|
@ -81,42 +81,6 @@ public class MainActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initBottomSheet() {
|
|
||||||
bottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.player_bottom_sheet));
|
|
||||||
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback);
|
|
||||||
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
|
|
||||||
|
|
||||||
isBottomSheetInPeek(mainViewModel.isQueueLoaded());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isBottomSheetInPeek(Boolean isVisible) {
|
|
||||||
|
|
||||||
Log.d(TAG, "isBottomSheetInPeek: " + isVisible);
|
|
||||||
|
|
||||||
if (isVisible) {
|
|
||||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
|
||||||
} else {
|
|
||||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initNavigation() {
|
|
||||||
bottomNavigationView = findViewById(R.id.bottom_navigation);
|
|
||||||
navHostFragment = (NavHostFragment) fragmentManager.findFragmentById(R.id.nav_host_fragment);
|
|
||||||
navController = navHostFragment.getNavController();
|
|
||||||
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
|
|
||||||
if(bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED && (
|
|
||||||
destination.getId() == R.id.homeFragment ||
|
|
||||||
destination.getId() == R.id.libraryFragment ||
|
|
||||||
destination.getId() == R.id.searchFragment ||
|
|
||||||
destination.getId() == R.id.settingsFragment)
|
|
||||||
) {
|
|
||||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
NavigationUI.setupWithNavController(bottomNavigationView, navController);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkPreviousSession() {
|
private void checkPreviousSession() {
|
||||||
App.getApiClientInstance(getApplicationContext()).ChangeServerLocation(PreferenceUtil.getInstance(this).getServer());
|
App.getApiClientInstance(getApplicationContext()).ChangeServerLocation(PreferenceUtil.getInstance(this).getServer());
|
||||||
App.getApiClientInstance(getApplicationContext()).SetAuthenticationInfo(PreferenceUtil.getInstance(this).getToken(), PreferenceUtil.getInstance(this).getUser());
|
App.getApiClientInstance(getApplicationContext()).SetAuthenticationInfo(PreferenceUtil.getInstance(this).getToken(), PreferenceUtil.getInstance(this).getUser());
|
||||||
|
|
@ -140,6 +104,49 @@ public class MainActivity extends BaseActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// BOTTOM SHEET/NAVIGATION
|
||||||
|
private void initBottomSheet() {
|
||||||
|
bottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.player_bottom_sheet));
|
||||||
|
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback);
|
||||||
|
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All'apertura mostro il bottom sheet solo se in coda c'è qualcosa
|
||||||
|
*/
|
||||||
|
isBottomSheetInPeek(mainViewModel.isQueueLoaded());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void isBottomSheetInPeek(Boolean isVisible) {
|
||||||
|
if (isVisible) {
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||||
|
} else {
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initNavigation() {
|
||||||
|
bottomNavigationView = findViewById(R.id.bottom_navigation);
|
||||||
|
navHostFragment = (NavHostFragment) fragmentManager.findFragmentById(R.id.nav_host_fragment);
|
||||||
|
navController = navHostFragment.getNavController();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In questo modo intercetto il cambio schermata tramite navbar e se il bottom sheet è aperto,
|
||||||
|
* lo chiudo
|
||||||
|
*/
|
||||||
|
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
|
||||||
|
if(bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED && (
|
||||||
|
destination.getId() == R.id.homeFragment ||
|
||||||
|
destination.getId() == R.id.libraryFragment ||
|
||||||
|
destination.getId() == R.id.searchFragment ||
|
||||||
|
destination.getId() == R.id.settingsFragment)
|
||||||
|
) {
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
NavigationUI.setupWithNavController(bottomNavigationView, navController);
|
||||||
|
}
|
||||||
|
|
||||||
public void setBottomNavigationBarVisibility(boolean visibility) {
|
public void setBottomNavigationBarVisibility(boolean visibility) {
|
||||||
if (visibility) {
|
if (visibility) {
|
||||||
bottomNavigationView.setVisibility(View.VISIBLE);
|
bottomNavigationView.setVisibility(View.VISIBLE);
|
||||||
|
|
@ -161,7 +168,7 @@ public class MainActivity extends BaseActivity {
|
||||||
@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_COLLAPSED:
|
case BottomSheetBehavior.STATE_SETTLING:
|
||||||
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
||||||
if(playerBottomSheetFragment == null) break;
|
if(playerBottomSheetFragment == null) break;
|
||||||
|
|
||||||
|
|
@ -184,6 +191,18 @@ public class MainActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scroll on top del bottom sheet quando chiudo
|
||||||
|
* In questo modo non mi ritrovo al posto dell'header una parte centrale del player
|
||||||
|
*/
|
||||||
|
public void setBottomSheetMusicInfo(Song song) {
|
||||||
|
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
||||||
|
if(playerBottomSheetFragment == null) return;
|
||||||
|
|
||||||
|
playerBottomSheetFragment.scrollPager(song, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NAVIGATION
|
||||||
public void goToLogin() {
|
public void goToLogin() {
|
||||||
if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment)
|
if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment)
|
||||||
navController.navigate(R.id.action_landingFragment_to_loginFragment);
|
navController.navigate(R.id.action_landingFragment_to_loginFragment);
|
||||||
|
|
@ -225,6 +244,15 @@ public class MainActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if(bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED)
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||||
|
else
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// CONNECTION
|
||||||
private void connectivityStatusReceiverManager(boolean isActive) {
|
private void connectivityStatusReceiverManager(boolean isActive) {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
|
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||||
|
|
@ -233,12 +261,4 @@ public class MainActivity extends BaseActivity {
|
||||||
unregisterReceiver(connectivityStatusBroadcastReceiver);
|
unregisterReceiver(connectivityStatusBroadcastReceiver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
if(bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED)
|
|
||||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
|
||||||
else
|
|
||||||
super.onBackPressed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -5,10 +5,10 @@ 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;
|
||||||
import androidx.core.view.ViewCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
@ -20,6 +20,7 @@ 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 {
|
||||||
|
|
@ -32,6 +33,8 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
private PlayerNowPlayingSongAdapter playerNowPlayingSongAdapter;
|
private PlayerNowPlayingSongAdapter playerNowPlayingSongAdapter;
|
||||||
private PlayerSongQueueAdapter playerSongQueueAdapter;
|
private PlayerSongQueueAdapter playerSongQueueAdapter;
|
||||||
|
|
||||||
|
private boolean isNowPlaying = false;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
|
@ -43,64 +46,59 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
|
|
||||||
initQueueSlideView();
|
initQueueSlideView();
|
||||||
initQueueRecyclerView();
|
initQueueRecyclerView();
|
||||||
|
initFavoriteButtonClick();
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initQueueSlideView() {
|
private void initQueueSlideView() {
|
||||||
bind.playerSongCoverViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
|
bind.playerBodyLayout.playerSongCoverViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
|
||||||
|
|
||||||
playerNowPlayingSongAdapter = new PlayerNowPlayingSongAdapter(requireContext());
|
playerNowPlayingSongAdapter = new PlayerNowPlayingSongAdapter(requireContext());
|
||||||
bind.playerSongCoverViewPager.setAdapter(playerNowPlayingSongAdapter);
|
bind.playerBodyLayout.playerSongCoverViewPager.setAdapter(playerNowPlayingSongAdapter);
|
||||||
playerBottomSheetViewModel.getQueueSong().observe(requireActivity(), songs -> playerNowPlayingSongAdapter.setItems(songs));
|
playerBottomSheetViewModel.getQueueSong().observe(requireActivity(), songs -> playerNowPlayingSongAdapter.setItems(songs));
|
||||||
|
|
||||||
bind.playerSongCoverViewPager.setOffscreenPageLimit(3);
|
bind.playerBodyLayout.playerSongCoverViewPager.setOffscreenPageLimit(3);
|
||||||
setDiscoverSongSlideViewOffset(40, 4);
|
bind.playerBodyLayout.playerSongCoverViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||||
|
|
||||||
bind.playerSongCoverViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
public void onPageSelected(int position) {
|
||||||
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
|
super.onPageSelected(position);
|
||||||
|
|
||||||
playerBottomSheetViewModel.setNowPlayingSong(position);
|
Song song = playerNowPlayingSongAdapter.getItem(position);
|
||||||
|
if (song != null && song != playerBottomSheetViewModel.getSong()) setSongInfo(song);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
playerBottomSheetViewModel.getNowPlayingSong().observe(requireActivity(), song -> {
|
|
||||||
if(song != null)
|
|
||||||
setSongInfo(song);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initQueueRecyclerView() {
|
private void initQueueRecyclerView() {
|
||||||
bind.playerQueueRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.playerBodyLayout.playerQueueRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.playerQueueRecyclerView.setHasFixedSize(true);
|
bind.playerBodyLayout.playerQueueRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
playerSongQueueAdapter = new PlayerSongQueueAdapter(activity, requireContext(), getChildFragmentManager());
|
playerSongQueueAdapter = new PlayerSongQueueAdapter(requireContext(), this);
|
||||||
bind.playerQueueRecyclerView.setAdapter(playerSongQueueAdapter);
|
bind.playerBodyLayout.playerQueueRecyclerView.setAdapter(playerSongQueueAdapter);
|
||||||
playerBottomSheetViewModel.getQueueSong().observe(requireActivity(), songs -> playerSongQueueAdapter.setItems(songs));
|
playerBottomSheetViewModel.getQueueSong().observe(requireActivity(), songs -> playerSongQueueAdapter.setItems(songs));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDiscoverSongSlideViewOffset(float pageOffset, float pageMargin) {
|
private void initFavoriteButtonClick() {
|
||||||
bind.playerSongCoverViewPager.setPageTransformer((page, position) -> {
|
bind.playerBodyLayout.buttonFavorite.setOnClickListener(v -> playerBottomSheetViewModel.setFavorite());
|
||||||
float myOffset = position * -(2 * pageOffset + pageMargin);
|
|
||||||
if (bind.playerSongCoverViewPager.getOrientation() == ViewPager2.ORIENTATION_HORIZONTAL) {
|
|
||||||
if (ViewCompat.getLayoutDirection(bind.playerSongCoverViewPager) == ViewCompat.LAYOUT_DIRECTION_RTL) {
|
|
||||||
page.setTranslationX(-myOffset);
|
|
||||||
} else {
|
|
||||||
page.setTranslationX(myOffset);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
page.setTranslationY(myOffset);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSongInfo(Song song) {
|
private void setSongInfo(Song song) {
|
||||||
if(song != null) {
|
playerBottomSheetViewModel.setNowPlayingSong(song);
|
||||||
bind.playerSongTitleLabel.setText(song.getTitle());
|
|
||||||
bind.playerArtistNameLabel.setText(song.getArtistName());
|
bind.playerBodyLayout.playerSongTitleLabel.setText(song.getTitle());
|
||||||
}
|
bind.playerBodyLayout.playerArtistNameLabel.setText(song.getArtistName());
|
||||||
|
|
||||||
|
bind.playerHeaderLayout.playerHeaderSongTitleLabel.setText(song.getTitle());
|
||||||
|
bind.playerHeaderLayout.playerHeaderSongArtistLabel.setText(song.getArtistName());
|
||||||
|
|
||||||
|
bind.playerBodyLayout.buttonFavorite.setChecked(song.isFavorite());
|
||||||
|
|
||||||
|
playSong(song);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playSong(Song song) {
|
||||||
|
Toast.makeText(activity, MusicUtil.getSongFileUri(song), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public View getPlayerHeader() {
|
public View getPlayerHeader() {
|
||||||
|
|
@ -110,4 +108,9 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
public void scrollOnTop() {
|
public void scrollOnTop() {
|
||||||
bind.playerNestedScrollView.fullScroll(ScrollView.FOCUS_UP);
|
bind.playerNestedScrollView.fullScroll(ScrollView.FOCUS_UP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void scrollPager(Song song, int page, boolean smoothScroll) {
|
||||||
|
bind.playerBodyLayout.playerSongCoverViewPager.setCurrentItem(page, smoothScroll);
|
||||||
|
setSongInfo(song);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.cappielloantonio.play.util;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.cappielloantonio.play.App;
|
||||||
|
import com.cappielloantonio.play.model.DirectPlayCodec;
|
||||||
|
import com.cappielloantonio.play.model.Song;
|
||||||
|
|
||||||
|
import org.jellyfin.apiclient.interaction.ApiClient;
|
||||||
|
|
||||||
|
public class MusicUtil {
|
||||||
|
public static String getSongFileUri(Song song) {
|
||||||
|
ApiClient apiClient = App.getApiClientInstance(App.getInstance());
|
||||||
|
PreferenceUtil preferenceUtil = PreferenceUtil.getInstance(App.getInstance());
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(256);
|
||||||
|
builder.append(apiClient.getApiUrl());
|
||||||
|
builder.append("/Audio/");
|
||||||
|
builder.append(song.getId());
|
||||||
|
builder.append("/universal");
|
||||||
|
builder.append("?UserId=").append(apiClient.getCurrentUserId());
|
||||||
|
builder.append("&DeviceId=").append(apiClient.getDeviceId());
|
||||||
|
|
||||||
|
// web client maximum is 12444445 and 320kbps is 320000
|
||||||
|
builder.append("&MaxStreamingBitrate=").append(preferenceUtil.getMaximumBitrate());
|
||||||
|
|
||||||
|
boolean containerAdded = false;
|
||||||
|
for (DirectPlayCodec directPlayCodec : preferenceUtil.getDirectPlayCodecs()) {
|
||||||
|
if (directPlayCodec.selected) {
|
||||||
|
if (!containerAdded) {
|
||||||
|
builder.append("&Container=");
|
||||||
|
containerAdded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append(directPlayCodec.codec.value).append(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containerAdded) {
|
||||||
|
// remove last comma
|
||||||
|
builder.deleteCharAt(builder.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("&TranscodingContainer=ts");
|
||||||
|
builder.append("&TranscodingProtocol=hls");
|
||||||
|
|
||||||
|
// preferred codec when transcoding
|
||||||
|
builder.append("&AudioCodec=").append(preferenceUtil.getTranscodeCodec());
|
||||||
|
builder.append("&api_key=").append(apiClient.getAccessToken());
|
||||||
|
|
||||||
|
Log.i(MusicUtil.class.getName(), "playing audio: " + builder);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,12 @@ import android.content.SharedPreferences;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.cappielloantonio.play.helper.ThemeHelper;
|
import com.cappielloantonio.play.helper.ThemeHelper;
|
||||||
|
import com.cappielloantonio.play.model.DirectPlayCodec;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class PreferenceUtil {
|
public class PreferenceUtil {
|
||||||
public static final String SERVER = "server";
|
public static final String SERVER = "server";
|
||||||
|
|
@ -19,6 +25,10 @@ public class PreferenceUtil {
|
||||||
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 TRANSCODE_CODEC = "transcode_codec";
|
||||||
|
public static final String DIRECT_PLAY_CODECS = "direct_play_codecs";
|
||||||
|
public static final String MAXIMUM_BITRATE = "maximum_bitrate";
|
||||||
|
|
||||||
private static PreferenceUtil sInstance;
|
private static PreferenceUtil sInstance;
|
||||||
|
|
||||||
private final SharedPreferences mPreferences;
|
private final SharedPreferences mPreferences;
|
||||||
|
|
@ -104,4 +114,46 @@ public class PreferenceUtil {
|
||||||
public final int getImageCacheSize() {
|
public final int getImageCacheSize() {
|
||||||
return Integer.parseInt(mPreferences.getString(IMAGE_CACHE_SIZE, "400000000"));
|
return Integer.parseInt(mPreferences.getString(IMAGE_CACHE_SIZE, "400000000"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final String getTranscodeCodec() {
|
||||||
|
return mPreferences.getString(TRANSCODE_CODEC, "aac");
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getMaximumBitrate() {
|
||||||
|
return mPreferences.getString(MAXIMUM_BITRATE, "10000000");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DirectPlayCodec> getDirectPlayCodecs() {
|
||||||
|
DirectPlayCodec.Codec[] codecs = DirectPlayCodec.Codec.values();
|
||||||
|
|
||||||
|
Set<String> selectedCodecNames = new HashSet<>();
|
||||||
|
for (DirectPlayCodec.Codec codec : codecs) {
|
||||||
|
// this will be the default value
|
||||||
|
selectedCodecNames.add(codec.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedCodecNames = mPreferences.getStringSet(DIRECT_PLAY_CODECS, selectedCodecNames);
|
||||||
|
|
||||||
|
ArrayList<DirectPlayCodec> directPlayCodecs = new ArrayList<>();
|
||||||
|
for (DirectPlayCodec.Codec codec : codecs) {
|
||||||
|
String name = codec.name();
|
||||||
|
boolean selected = selectedCodecNames.contains(name);
|
||||||
|
directPlayCodecs.add(new DirectPlayCodec(codec, selected));
|
||||||
|
}
|
||||||
|
|
||||||
|
return directPlayCodecs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirectPlayCodecs(List<DirectPlayCodec> directPlayCodecs) {
|
||||||
|
Set<String> codecNames = new HashSet<>();
|
||||||
|
for (DirectPlayCodec directPlayCodec : directPlayCodecs) {
|
||||||
|
if (directPlayCodec.selected) {
|
||||||
|
codecNames.add(directPlayCodec.codec.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final SharedPreferences.Editor editor = mPreferences.edit();
|
||||||
|
editor.putStringSet(DIRECT_PLAY_CODECS, codecNames);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,14 +8,14 @@ import java.util.List;
|
||||||
|
|
||||||
public class QueueUtil {
|
public class QueueUtil {
|
||||||
public static Queue getQueueElementFromSong(Song song) {
|
public static Queue getQueueElementFromSong(Song song) {
|
||||||
return new Queue(song.getId());
|
return new Queue(song.getId(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Queue> getQueueElementsFromSongs(List<Song> songs) {
|
public static List<Queue> getQueueElementsFromSongs(List<Song> songs) {
|
||||||
List<Queue> queue = new ArrayList<>();
|
List<Queue> queue = new ArrayList<>();
|
||||||
|
|
||||||
for(Song song: songs) {
|
for(Song song: songs) {
|
||||||
queue.add(new Queue(song.getId()));
|
queue.add(new Queue(song.getId(), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
return queue;
|
return queue;
|
||||||
|
|
|
||||||
|
|
@ -5,24 +5,25 @@ import android.app.Application;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import com.cappielloantonio.play.model.Song;
|
import com.cappielloantonio.play.model.Song;
|
||||||
import com.cappielloantonio.play.repository.QueueRepository;
|
import com.cappielloantonio.play.repository.QueueRepository;
|
||||||
|
import com.cappielloantonio.play.repository.SongRepository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||||
private static final String TAG = "HomeViewModel";
|
private static final String TAG = "PlayerBottomSheetViewModel";
|
||||||
|
private SongRepository songRepository;
|
||||||
private QueueRepository queueRepository;
|
private QueueRepository queueRepository;
|
||||||
|
|
||||||
private LiveData<List<Song>> queueSong;
|
private LiveData<List<Song>> queueSong;
|
||||||
private LiveData<Song> nowPlayingSong = new MutableLiveData<>();
|
private Song song;
|
||||||
|
|
||||||
|
|
||||||
public PlayerBottomSheetViewModel(@NonNull Application application) {
|
public PlayerBottomSheetViewModel(@NonNull Application application) {
|
||||||
super(application);
|
super(application);
|
||||||
|
|
||||||
|
songRepository = new SongRepository(application);
|
||||||
queueRepository = new QueueRepository(application);
|
queueRepository = new QueueRepository(application);
|
||||||
|
|
||||||
queueSong = queueRepository.getLiveQueue();
|
queueSong = queueRepository.getLiveQueue();
|
||||||
|
|
@ -32,17 +33,24 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||||
return queueSong;
|
return queueSong;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Song> getNowPlayingSong() {
|
public void setNowPlayingSong(Song song) {
|
||||||
return nowPlayingSong;
|
this.song = song;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Song> setNowPlayingSong(int position) {
|
public void setFavorite() {
|
||||||
Song song = queueRepository.getSongs().get(position);
|
if(song.isFavorite())
|
||||||
|
song.setFavorite(false);
|
||||||
|
else
|
||||||
|
song.setFavorite(true);
|
||||||
|
|
||||||
if(song != null) {
|
songRepository.setFavoriteStatus(song);
|
||||||
nowPlayingSong = new MutableLiveData<>(song);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nowPlayingSong;
|
public Song getSong() {
|
||||||
|
return song;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSong(Song song) {
|
||||||
|
this.song = song;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,112 +21,13 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<include
|
||||||
android:id="@+id/player_big_layout"
|
android:id="@+id/player_body_layout"
|
||||||
|
layout="@layout/player_body_bottom_sheet"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/collapse_bottom_sheet_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:paddingTop="20dp"
|
|
||||||
android:text="Now playing"
|
|
||||||
android:textColor="@color/titleTextColor"
|
|
||||||
android:textSize="22sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
|
||||||
android:id="@+id/player_song_cover_view_pager"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
app:layout_constraintDimensionRatio="H,1:1"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/collapse_bottom_sheet_button" />
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/player_big_progress_bar"
|
|
||||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginStart="28dp"
|
|
||||||
android:layout_marginTop="18dp"
|
|
||||||
android:layout_marginEnd="28dp"
|
|
||||||
android:progress="50"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_song_cover_view_pager" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/player_song_title_label"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:layout_marginTop="18dp"
|
|
||||||
android:text="@string/label_placeholder"
|
|
||||||
android:textColor="@color/titleTextColor"
|
|
||||||
android:textSize="22sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_big_progress_bar" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/player_artist_name_label"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:text="@string/label_placeholder"
|
|
||||||
android:textColor="@color/subtitleTextColor"
|
|
||||||
android:textSize="14sp"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_song_title_label" />
|
|
||||||
|
|
||||||
<ToggleButton
|
|
||||||
android:id="@+id/button_favorite"
|
|
||||||
android:layout_width="26dp"
|
|
||||||
android:layout_height="26dp"
|
|
||||||
android:background="@drawable/button_favorite_selector"
|
|
||||||
android:checked="false"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:text=""
|
|
||||||
android:textOff=""
|
|
||||||
android:textOn=""
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_big_progress_bar" />
|
|
||||||
|
|
||||||
|
|
||||||
<View style="@style/Divider"
|
|
||||||
android:id="@+id/player_divider"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:layout_marginStart="18dp"
|
|
||||||
android:layout_marginEnd="18dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_artist_name_label"/>
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/player_queue_recycler_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:paddingTop="18dp"
|
|
||||||
android:paddingBottom="@dimen/global_padding_bottom"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_divider"/>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
@ -99,15 +99,15 @@
|
||||||
android:id="@+id/search_result_nested_scroll_view"
|
android:id="@+id/search_result_nested_scroll_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone"
|
android:visibility="gone">
|
||||||
android:paddingBottom="@dimen/global_padding_bottom">
|
|
||||||
|
|
||||||
<!-- Search result -->
|
<!-- Search result -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/search_result_layout"
|
android:id="@+id/search_result_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="@dimen/global_padding_bottom">
|
||||||
|
|
||||||
<!-- Label -->
|
<!-- Label -->
|
||||||
<TextView
|
<TextView
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/player_big_layout"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/collapse_bottom_sheet_button"
|
android:id="@+id/collapse_bottom_sheet_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:paddingStart="20dp"
|
android:paddingStart="24dp"
|
||||||
android:paddingTop="20dp"
|
android:paddingTop="20dp"
|
||||||
android:text="Now playing"
|
android:text="Now playing"
|
||||||
android:textColor="@color/titleTextColor"
|
android:textColor="@color/titleTextColor"
|
||||||
|
|
@ -23,9 +26,7 @@
|
||||||
android:id="@+id/player_song_cover_view_pager"
|
android:id="@+id/player_song_cover_view_pager"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_marginTop="20dp"
|
android:layout_marginTop="20dp"
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
app:layout_constraintDimensionRatio="H,1:1"
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
|
@ -37,51 +38,80 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_marginStart="20dp"
|
android:layout_marginStart="28dp"
|
||||||
android:layout_marginTop="18dp"
|
android:layout_marginTop="18dp"
|
||||||
android:layout_marginEnd="20dp"
|
android:layout_marginEnd="28dp"
|
||||||
android:progress="50"
|
android:progress="50"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_song_cover_view_pager" />
|
app:layout_constraintTop_toBottomOf="@+id/player_song_cover_view_pager" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/player_song_title_label"
|
android:id="@+id/head_title_favorite_linear_layout"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="20dp"
|
|
||||||
android:layout_marginTop="18dp"
|
|
||||||
android:text="@string/label_placeholder"
|
|
||||||
android:textColor="@color/titleTextColor"
|
|
||||||
android:textSize="22sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_big_progress_bar" />
|
app:layout_constraintTop_toBottomOf="@+id/player_big_progress_bar"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/player_song_title_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginTop="18dp"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
|
android:textColor="@color/titleTextColor"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<ToggleButton
|
||||||
|
android:id="@+id/button_favorite"
|
||||||
|
android:layout_width="26dp"
|
||||||
|
android:layout_height="26dp"
|
||||||
|
android:background="@drawable/button_favorite_selector"
|
||||||
|
android:checked="false"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginTop="18dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:text=""
|
||||||
|
android:textOff=""
|
||||||
|
android:textOn=""
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/player_big_progress_bar" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/player_artist_name_label"
|
android:id="@+id/player_artist_name_label"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="20dp"
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
android:text="@string/label_placeholder"
|
android:text="@string/label_placeholder"
|
||||||
android:textColor="@color/subtitleTextColor"
|
android:textColor="@color/subtitleTextColor"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_song_title_label" />
|
app:layout_constraintTop_toBottomOf="@+id/head_title_favorite_linear_layout" />
|
||||||
|
|
||||||
<ToggleButton
|
<View style="@style/Divider"
|
||||||
android:id="@+id/button_favorite"
|
android:id="@+id/player_divider"
|
||||||
android:layout_width="26dp"
|
android:layout_marginTop="24dp"
|
||||||
android:layout_height="26dp"
|
android:layout_marginStart="18dp"
|
||||||
android:background="@drawable/button_favorite_selector"
|
android:layout_marginEnd="18dp"
|
||||||
android:checked="false"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
android:text=""
|
|
||||||
android:textOff=""
|
|
||||||
android:textOn=""
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_big_progress_bar" />
|
app:layout_constraintTop_toBottomOf="@+id/player_artist_name_label"/>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/player_queue_recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingTop="18dp"
|
||||||
|
android:paddingBottom="18dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/player_divider"/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -19,26 +19,30 @@
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/player_header_song_title_label"
|
android:id="@+id/player_header_song_title_label"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
app:layout_constraintStart_toEndOf="@+id/player_header_button"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
android:text="@string/label_placeholder"
|
|
||||||
android:paddingStart="12dp"
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
android:textColor="@color/titleTextColor"
|
android:textColor="@color/titleTextColor"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/player_header_button"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/player_header_song_artist_label"
|
android:id="@+id/player_header_song_artist_label"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="@font/open_sans_font_family"
|
android:fontFamily="@font/open_sans_font_family"
|
||||||
android:paddingStart="12dp"
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
android:textColor="@color/subtitleTextColor"
|
android:textColor="@color/subtitleTextColor"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:text="@string/label_placeholder"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/player_header_button"
|
app:layout_constraintStart_toEndOf="@+id/player_header_button"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/player_header_song_title_label" />
|
app:layout_constraintTop_toBottomOf="@+id/player_header_song_title_label" />
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue