From dbb6250c06bb78b163b5bf127b69f72a81b07873 Mon Sep 17 00:00:00 2001 From: tkashkin Date: Thu, 18 May 2017 15:06:27 +0300 Subject: [PATCH] Add synchronized lyrics support (only LRC format at the moment) --- .../model/lyrics/SynchronizedLyrics.java | 32 +++++++++ .../model/lyrics/SynchronizedLyricsLRC.java | 47 ++++++++++++++ .../player/PlayerAlbumCoverFragment.java | 65 ++++++++++++++++++- .../player/card/CardPlayerFragment.java | 3 + .../player/flat/FlatPlayerFragment.java | 3 + .../layout/fragment_player_album_cover.xml | 35 ++++++++++ 6 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/model/lyrics/SynchronizedLyrics.java create mode 100644 app/src/main/java/com/kabouzeid/gramophone/model/lyrics/SynchronizedLyricsLRC.java diff --git a/app/src/main/java/com/kabouzeid/gramophone/model/lyrics/SynchronizedLyrics.java b/app/src/main/java/com/kabouzeid/gramophone/model/lyrics/SynchronizedLyrics.java new file mode 100644 index 00000000..d0a6d748 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/model/lyrics/SynchronizedLyrics.java @@ -0,0 +1,32 @@ +package com.kabouzeid.gramophone.model.lyrics; + +import android.util.SparseArray; + +public abstract class SynchronizedLyrics { + public final SparseArray 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); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/model/lyrics/SynchronizedLyricsLRC.java b/app/src/main/java/com/kabouzeid/gramophone/model/lyrics/SynchronizedLyricsLRC.java new file mode 100644 index 00000000..b59ebb9b --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/model/lyrics/SynchronizedLyricsLRC.java @@ -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); + } + } + } + } + +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/PlayerAlbumCoverFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/PlayerAlbumCoverFragment.java index 2503281d..388b5a83 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/PlayerAlbumCoverFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/PlayerAlbumCoverFragment.java @@ -10,12 +10,16 @@ import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.TextView; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.adapter.AlbumCoverPagerAdapter; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; +import com.kabouzeid.gramophone.helper.MusicProgressViewUpdateHelper; import com.kabouzeid.gramophone.misc.SimpleAnimatorListener; +import com.kabouzeid.gramophone.model.lyrics.SynchronizedLyrics; import com.kabouzeid.gramophone.ui.fragments.AbsMusicServiceFragment; import com.kabouzeid.gramophone.util.ViewUtil; @@ -26,7 +30,7 @@ import butterknife.Unbinder; /** * @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(); private Unbinder unbinder; @@ -36,9 +40,19 @@ public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements @BindView(R.id.player_favorite_icon) 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 int currentPosition; + private SynchronizedLyrics synchronizedLyrics; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -68,6 +82,8 @@ public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements return gestureDetector.onTouchEvent(event); } }); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + progressViewUpdateHelper.start(); } @Override @@ -75,6 +91,7 @@ public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements super.onDestroyView(); viewPager.removeOnPageChangeListener(this); unbinder.unbind(); + progressViewUpdateHelper.stop(); } @Override @@ -163,6 +180,22 @@ public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements .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) { if (callbacks != null) callbacks.onColorChanged(color); } @@ -171,6 +204,36 @@ public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements 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 { void onColorChanged(int color); diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/card/CardPlayerFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/card/CardPlayerFragment.java index 3d002a18..ee1aa02f 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/card/CardPlayerFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/card/CardPlayerFragment.java @@ -42,6 +42,7 @@ import com.kabouzeid.gramophone.dialogs.SongShareDialog; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.menu.SongMenuHelper; 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.fragments.player.AbsPlayerFragment; import com.kabouzeid.gramophone.ui.fragments.player.PlayerAlbumCoverFragment; @@ -309,6 +310,7 @@ public class CardPlayerFragment extends AbsPlayerFragment implements PlayerAlbum protected void onPreExecute() { super.onPreExecute(); lyricsInfo = null; + playerAlbumCoverFragment.setSynchronizedLyrics(null); toolbar.getMenu().removeItem(R.id.action_show_lyrics); } @@ -333,6 +335,7 @@ public class CardPlayerFragment extends AbsPlayerFragment implements PlayerAlbum } } else { lyricsInfo = new LyricsDialog.LyricInfo(song.title, lyrics); + playerAlbumCoverFragment.setSynchronizedLyrics(SynchronizedLyrics.parse(lyrics)); Activity activity = getActivity(); if (toolbar != null && activity != null) if (toolbar.getMenu().findItem(R.id.action_show_lyrics) == null) { diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/flat/FlatPlayerFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/flat/FlatPlayerFragment.java index 434849ba..c52c2dfd 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/flat/FlatPlayerFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/player/flat/FlatPlayerFragment.java @@ -40,6 +40,7 @@ import com.kabouzeid.gramophone.dialogs.SongShareDialog; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.menu.SongMenuHelper; 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.fragments.player.AbsPlayerFragment; import com.kabouzeid.gramophone.ui.fragments.player.PlayerAlbumCoverFragment; @@ -305,6 +306,7 @@ public class FlatPlayerFragment extends AbsPlayerFragment implements PlayerAlbum protected void onPreExecute() { super.onPreExecute(); lyricsInfo = null; + playerAlbumCoverFragment.setSynchronizedLyrics(null); toolbar.getMenu().removeItem(R.id.action_show_lyrics); } @@ -329,6 +331,7 @@ public class FlatPlayerFragment extends AbsPlayerFragment implements PlayerAlbum } } else { lyricsInfo = new LyricsDialog.LyricInfo(song.title, lyrics); + playerAlbumCoverFragment.setSynchronizedLyrics(SynchronizedLyrics.parse(lyrics)); Activity activity = getActivity(); if (toolbar != null && activity != null) if (toolbar.getMenu().findItem(R.id.action_show_lyrics) == null) { diff --git a/app/src/main/res/layout/fragment_player_album_cover.xml b/app/src/main/res/layout/fragment_player_album_cover.xml index 03c14fc4..a47b37d7 100644 --- a/app/src/main/res/layout/fragment_player_album_cover.xml +++ b/app/src/main/res/layout/fragment_player_album_cover.xml @@ -10,6 +10,41 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + + + + + + +