Add synchronized lyrics support (only LRC format at the moment)
This commit is contained in:
parent
1c55d56093
commit
dbb6250c06
6 changed files with 184 additions and 1 deletions
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.kabouzeid.gramophone.model.lyrics;
|
||||||
|
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
public abstract class SynchronizedLyrics {
|
||||||
|
public final SparseArray<String> lines = new SparseArray<>();
|
||||||
|
|
||||||
|
public static SynchronizedLyrics parse(String data)
|
||||||
|
{
|
||||||
|
return new SynchronizedLyricsLRC(data); // no another formats at the moment
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLine(int time)
|
||||||
|
{
|
||||||
|
time += 500; // small time adjustment to display line before it actually starts
|
||||||
|
|
||||||
|
int lastLineTime = lines.keyAt(0);
|
||||||
|
|
||||||
|
for(int i = 0; i < lines.size(); i++) {
|
||||||
|
int lineTime = lines.keyAt(i);
|
||||||
|
|
||||||
|
if(time >= lineTime) {
|
||||||
|
lastLineTime = lineTime;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.get(lastLineTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.kabouzeid.gramophone.model.lyrics;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class SynchronizedLyricsLRC extends SynchronizedLyrics {
|
||||||
|
private static Pattern LRC_LINE_PATTERN = Pattern.compile("((?:\\[.*?\\])+)(.*)");
|
||||||
|
private static Pattern LRC_TIME_PATTERN = Pattern.compile("\\[(\\d\\d):(\\d\\d)(?:\\.(\\d\\d))\\]");
|
||||||
|
|
||||||
|
public SynchronizedLyricsLRC(String data)
|
||||||
|
{
|
||||||
|
if(data == null || data.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] lines = data.split("\r?\n");
|
||||||
|
|
||||||
|
for(String line : lines) {
|
||||||
|
line = line.trim();
|
||||||
|
if(line.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher matcher = SynchronizedLyricsLRC.LRC_LINE_PATTERN.matcher(line);
|
||||||
|
if(matcher.find()) {
|
||||||
|
String time = matcher.group(1);
|
||||||
|
String text = matcher.group(2);
|
||||||
|
|
||||||
|
Matcher timeMatcher = SynchronizedLyricsLRC.LRC_TIME_PATTERN.matcher(time);
|
||||||
|
while(timeMatcher.find()) {
|
||||||
|
int m = 0, s = 0, x = 0;
|
||||||
|
try {
|
||||||
|
m = Integer.parseInt(timeMatcher.group(1));
|
||||||
|
s = Integer.parseInt(timeMatcher.group(2));
|
||||||
|
x = Integer.parseInt(timeMatcher.group(3));
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
int ms = x*10 + s*1000 + m*60000;
|
||||||
|
|
||||||
|
this.lines.append(ms, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -10,12 +10,16 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.AccelerateInterpolator;
|
import android.view.animation.AccelerateInterpolator;
|
||||||
import android.view.animation.DecelerateInterpolator;
|
import android.view.animation.DecelerateInterpolator;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.kabouzeid.gramophone.R;
|
import com.kabouzeid.gramophone.R;
|
||||||
import com.kabouzeid.gramophone.adapter.AlbumCoverPagerAdapter;
|
import com.kabouzeid.gramophone.adapter.AlbumCoverPagerAdapter;
|
||||||
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
|
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
|
||||||
|
import com.kabouzeid.gramophone.helper.MusicProgressViewUpdateHelper;
|
||||||
import com.kabouzeid.gramophone.misc.SimpleAnimatorListener;
|
import com.kabouzeid.gramophone.misc.SimpleAnimatorListener;
|
||||||
|
import com.kabouzeid.gramophone.model.lyrics.SynchronizedLyrics;
|
||||||
import com.kabouzeid.gramophone.ui.fragments.AbsMusicServiceFragment;
|
import com.kabouzeid.gramophone.ui.fragments.AbsMusicServiceFragment;
|
||||||
import com.kabouzeid.gramophone.util.ViewUtil;
|
import com.kabouzeid.gramophone.util.ViewUtil;
|
||||||
|
|
||||||
|
|
@ -26,7 +30,7 @@ import butterknife.Unbinder;
|
||||||
/**
|
/**
|
||||||
* @author Karim Abou Zeid (kabouzeid)
|
* @author Karim Abou Zeid (kabouzeid)
|
||||||
*/
|
*/
|
||||||
public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements ViewPager.OnPageChangeListener {
|
public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements ViewPager.OnPageChangeListener, MusicProgressViewUpdateHelper.Callback {
|
||||||
public static final String TAG = PlayerAlbumCoverFragment.class.getSimpleName();
|
public static final String TAG = PlayerAlbumCoverFragment.class.getSimpleName();
|
||||||
|
|
||||||
private Unbinder unbinder;
|
private Unbinder unbinder;
|
||||||
|
|
@ -36,9 +40,19 @@ public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements
|
||||||
@BindView(R.id.player_favorite_icon)
|
@BindView(R.id.player_favorite_icon)
|
||||||
ImageView favoriteIcon;
|
ImageView favoriteIcon;
|
||||||
|
|
||||||
|
@BindView(R.id.player_lyrics)
|
||||||
|
FrameLayout lyrics;
|
||||||
|
@BindView(R.id.player_lyrics_line1)
|
||||||
|
TextView lyricsLine1;
|
||||||
|
@BindView(R.id.player_lyrics_line2)
|
||||||
|
TextView lyricsLine2;
|
||||||
|
|
||||||
private Callbacks callbacks;
|
private Callbacks callbacks;
|
||||||
private int currentPosition;
|
private int currentPosition;
|
||||||
|
|
||||||
|
private SynchronizedLyrics synchronizedLyrics;
|
||||||
|
private MusicProgressViewUpdateHelper progressViewUpdateHelper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
|
|
@ -68,6 +82,8 @@ public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements
|
||||||
return gestureDetector.onTouchEvent(event);
|
return gestureDetector.onTouchEvent(event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this);
|
||||||
|
progressViewUpdateHelper.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -75,6 +91,7 @@ public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
viewPager.removeOnPageChangeListener(this);
|
viewPager.removeOnPageChangeListener(this);
|
||||||
unbinder.unbind();
|
unbinder.unbind();
|
||||||
|
progressViewUpdateHelper.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -163,6 +180,22 @@ public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSynchronizedLyrics(SynchronizedLyrics sLyrics)
|
||||||
|
{
|
||||||
|
if(sLyrics == null || sLyrics.lines.size() == 0)
|
||||||
|
{
|
||||||
|
synchronizedLyrics = null;
|
||||||
|
lyrics.setVisibility(View.GONE);
|
||||||
|
lyricsLine1.setText(null);
|
||||||
|
lyricsLine2.setText(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronizedLyrics = sLyrics;
|
||||||
|
|
||||||
|
lyrics.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
private void notifyColorChange(int color) {
|
private void notifyColorChange(int color) {
|
||||||
if (callbacks != null) callbacks.onColorChanged(color);
|
if (callbacks != null) callbacks.onColorChanged(color);
|
||||||
}
|
}
|
||||||
|
|
@ -171,6 +204,36 @@ public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements
|
||||||
callbacks = listener;
|
callbacks = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdateProgressViews(int progress, int total) {
|
||||||
|
if(synchronizedLyrics == null || synchronizedLyrics.lines.size() == 0) {
|
||||||
|
lyricsLine1.setText(null);
|
||||||
|
lyricsLine2.setText(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String oldLine = lyricsLine2.getText().toString();
|
||||||
|
String line = synchronizedLyrics.getLine(progress);
|
||||||
|
|
||||||
|
if(!oldLine.equals(line) || oldLine.isEmpty())
|
||||||
|
{
|
||||||
|
lyricsLine1.setText(oldLine);
|
||||||
|
lyricsLine2.setText(line);
|
||||||
|
lyricsLine1.setVisibility(View.VISIBLE);
|
||||||
|
lyricsLine2.setVisibility(View.VISIBLE);
|
||||||
|
int l1h = lyricsLine1.getMeasuredHeight();
|
||||||
|
int l2h = lyricsLine2.getMeasuredHeight();
|
||||||
|
|
||||||
|
this.lyricsLine1.setAlpha(1f);
|
||||||
|
this.lyricsLine1.setTranslationY(0f);
|
||||||
|
this.lyricsLine1.animate().alpha(0f).translationY(-l1h).setDuration(300);
|
||||||
|
|
||||||
|
this.lyricsLine2.setAlpha(0f);
|
||||||
|
this.lyricsLine2.setTranslationY(l2h);
|
||||||
|
this.lyricsLine2.animate().alpha(1f).translationY(0f).setDuration(300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public interface Callbacks {
|
public interface Callbacks {
|
||||||
void onColorChanged(int color);
|
void onColorChanged(int color);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import com.kabouzeid.gramophone.dialogs.SongShareDialog;
|
||||||
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
|
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
|
||||||
import com.kabouzeid.gramophone.helper.menu.SongMenuHelper;
|
import com.kabouzeid.gramophone.helper.menu.SongMenuHelper;
|
||||||
import com.kabouzeid.gramophone.model.Song;
|
import com.kabouzeid.gramophone.model.Song;
|
||||||
|
import com.kabouzeid.gramophone.model.lyrics.SynchronizedLyrics;
|
||||||
import com.kabouzeid.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity;
|
import com.kabouzeid.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity;
|
||||||
import com.kabouzeid.gramophone.ui.fragments.player.AbsPlayerFragment;
|
import com.kabouzeid.gramophone.ui.fragments.player.AbsPlayerFragment;
|
||||||
import com.kabouzeid.gramophone.ui.fragments.player.PlayerAlbumCoverFragment;
|
import com.kabouzeid.gramophone.ui.fragments.player.PlayerAlbumCoverFragment;
|
||||||
|
|
@ -309,6 +310,7 @@ public class CardPlayerFragment extends AbsPlayerFragment implements PlayerAlbum
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
super.onPreExecute();
|
super.onPreExecute();
|
||||||
lyricsInfo = null;
|
lyricsInfo = null;
|
||||||
|
playerAlbumCoverFragment.setSynchronizedLyrics(null);
|
||||||
toolbar.getMenu().removeItem(R.id.action_show_lyrics);
|
toolbar.getMenu().removeItem(R.id.action_show_lyrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -333,6 +335,7 @@ public class CardPlayerFragment extends AbsPlayerFragment implements PlayerAlbum
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lyricsInfo = new LyricsDialog.LyricInfo(song.title, lyrics);
|
lyricsInfo = new LyricsDialog.LyricInfo(song.title, lyrics);
|
||||||
|
playerAlbumCoverFragment.setSynchronizedLyrics(SynchronizedLyrics.parse(lyrics));
|
||||||
Activity activity = getActivity();
|
Activity activity = getActivity();
|
||||||
if (toolbar != null && activity != null)
|
if (toolbar != null && activity != null)
|
||||||
if (toolbar.getMenu().findItem(R.id.action_show_lyrics) == null) {
|
if (toolbar.getMenu().findItem(R.id.action_show_lyrics) == null) {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import com.kabouzeid.gramophone.dialogs.SongShareDialog;
|
||||||
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
|
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
|
||||||
import com.kabouzeid.gramophone.helper.menu.SongMenuHelper;
|
import com.kabouzeid.gramophone.helper.menu.SongMenuHelper;
|
||||||
import com.kabouzeid.gramophone.model.Song;
|
import com.kabouzeid.gramophone.model.Song;
|
||||||
|
import com.kabouzeid.gramophone.model.lyrics.SynchronizedLyrics;
|
||||||
import com.kabouzeid.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity;
|
import com.kabouzeid.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity;
|
||||||
import com.kabouzeid.gramophone.ui.fragments.player.AbsPlayerFragment;
|
import com.kabouzeid.gramophone.ui.fragments.player.AbsPlayerFragment;
|
||||||
import com.kabouzeid.gramophone.ui.fragments.player.PlayerAlbumCoverFragment;
|
import com.kabouzeid.gramophone.ui.fragments.player.PlayerAlbumCoverFragment;
|
||||||
|
|
@ -305,6 +306,7 @@ public class FlatPlayerFragment extends AbsPlayerFragment implements PlayerAlbum
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
super.onPreExecute();
|
super.onPreExecute();
|
||||||
lyricsInfo = null;
|
lyricsInfo = null;
|
||||||
|
playerAlbumCoverFragment.setSynchronizedLyrics(null);
|
||||||
toolbar.getMenu().removeItem(R.id.action_show_lyrics);
|
toolbar.getMenu().removeItem(R.id.action_show_lyrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,6 +331,7 @@ public class FlatPlayerFragment extends AbsPlayerFragment implements PlayerAlbum
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lyricsInfo = new LyricsDialog.LyricInfo(song.title, lyrics);
|
lyricsInfo = new LyricsDialog.LyricInfo(song.title, lyrics);
|
||||||
|
playerAlbumCoverFragment.setSynchronizedLyrics(SynchronizedLyrics.parse(lyrics));
|
||||||
Activity activity = getActivity();
|
Activity activity = getActivity();
|
||||||
if (toolbar != null && activity != null)
|
if (toolbar != null && activity != null)
|
||||||
if (toolbar.getMenu().findItem(R.id.action_show_lyrics) == null) {
|
if (toolbar.getMenu().findItem(R.id.action_show_lyrics) == null) {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,41 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/player_lyrics"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:background="@drawable/shadow_up"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/player_lyrics_line1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textColor="@color/md_white_1000"
|
||||||
|
android:shadowColor="@color/md_black_1000"
|
||||||
|
android:shadowRadius="4"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/player_lyrics_line2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textColor="@color/md_white_1000"
|
||||||
|
android:shadowColor="@color/md_black_1000"
|
||||||
|
android:shadowRadius="4" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<android.support.v7.widget.AppCompatImageView
|
<android.support.v7.widget.AppCompatImageView
|
||||||
android:id="@+id/player_favorite_icon"
|
android:id="@+id/player_favorite_icon"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue