/* Copyright Statement:
*
* This software/firmware and related documentation ("MediaTek Software") are
* protected under relevant copyright laws. The information contained herein
* is confidential and proprietary to MediaTek Inc. and/or its licensors.
* Without the prior written permission of MediaTek inc. and/or its licensors,
* any reproduction, modification, use or disclosure of MediaTek Software,
* and information contained herein, in whole or in part, shall be strictly prohibited.
*/
/* MediaTek Inc. (C) 2010. All rights reserved.
*
* BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
* THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
* RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
* AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
* NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
* SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
* SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO SUCH
* THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY ACKNOWLEDGES
* THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES
* CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK
* SOFTWARE RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
* STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
* CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
* AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
* OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY RECEIVER TO
* MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
*
* The following software/firmware and/or related documentation ("MediaTek Software")
* have been modified by MediaTek Inc. All revisions are subject to any receiver's
* applicable license agreements with MediaTek Inc.
*/
package com.android.music;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.TranslateAnimation;
import java.lang.IllegalArgumentException;
import java.util.Scanner;
import java.util.regex.Pattern;
import com.android.music.R;
public class ScrollLrcView extends ScrollView {
/*
* static members
*/
private static final String LOG_TAG = "ScrollLrcView";
public static final int LRC_MODE_SINGLE = 0;
public static final int LRC_MODE_MULTIPLE = 1;
/*
* members
*/
private LinearLayout mLrcArea = null; // the lyric TextViews are placed under a LinearLayout.
private int mLrcMode = LRC_MODE_SINGLE; // single line, or multiple line
private LyricsBody mLrc = null; // the actual lyric object
private String mLastTrack = null;
private int[] mLyricPos = null; // the position (vertical) of each lyric sentences. in pixel
private int[] mLyricHeight = null; // the height of each lyric sentences (TextView). in pixel
private int mCurrIndex = -1; // where the lyrics has been scrolled to (by index)
private int mScrDensity = DisplayMetrics.DENSITY_MEDIUM;
/*
* constructors
* we should define all 3 constructors if we want to use this view in layout .xml files.
*/
public ScrollLrcView(Context context) {
this(context, null);
Log.v(LOG_TAG, "ScrollLrcView : constructed with (Context)");
}
public ScrollLrcView(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusable(false); // should set this. Otherwise the view will cause track ball behavior abnormal
DisplayMetrics dm = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(dm);
mScrDensity = dm.densityDpi;
resetLrcState();
Log.v(LOG_TAG, "ScrollLrcView : constructed with (Context, AttributeSet)");
}
public ScrollLrcView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setFocusable(false); // should set this. Otherwise the view will cause track ball behavior abnormal
DisplayMetrics dm = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(dm);
mScrDensity = dm.densityDpi;
resetLrcState();
Log.v(LOG_TAG, "ScrollLrcView : constructed with (Context, AttributeSet, int)");
}
/*
* after the ScrollLrcView is created, call this to load and setup lyrics
*
* [in]context: application context.
* [in]file: the absolute path-name of the audio sound track file.
* [in]lrcMode: the lyrics displaying mode (single-line / multiple-line)
* [in]duration: in milliseconds, the duration of the track being played.
*
* return value: true if the lyrics are loaded and parsed successfully.
*/
public boolean loadLyrics(Context context, String file) {
if (file.equals(mLastTrack)) { // if the same track, avoid loading it again
return true;
}
resetLrcState(); // reset first
String lrcfile = null;
Scanner scn = new Scanner(file);
// the lyric file, with surffix .lrc, should have the same name with the audio file.
// try to find a valid lyrics file for this track.
String tmp = scn.findInLine(Pattern.compile(".*/.*\\."));
if (null != tmp) {
lrcfile = new StringBuilder(tmp).append("lrc").toString();
mLrc = LyricsBody.getLyric(context, lrcfile);
}
return (mLrc != null);
}
/*
* when the first time the lyrics are loaded, the layout and views need to be setup.
*
* [in]context: application context.
* [in]file: the current file to be load for lyrics.
* [in]lrcMode: the lyrics displaying mode (single-line / multiple-line)
* [in]duration: in milliseconds, the duration of the track being played.
*
* return value: true if the lyrics view is setup successfully.
*/
public boolean setupLyrics(Context context, String file, int lrcMode, int duration) {
if (file == null) {
return false;
}
if (file.equals(mLastTrack)) { // if the same track, avoid to setup the ScrollView again
return true;
}
mLastTrack = new String(file);
return reSetupLyrics(context, lrcMode, duration);
}
/*
* when the lyrics mode (single-line / multiple-line) is changed, the layout and views need to
* be re-setup again.
*
* [in]context: application context.
* [in]lrcMode: the lyrics displaying mode (single-line / multiple-line)
* [in]duration: in milliseconds, the duration of the track being played.
*
* return value: true if the lyrics view is setup successfully.
*/
public boolean reSetupLyrics(Context context, int lrcMode, int duration) {
removeAllViews(); // clean up first
switch (lrcMode) {
case LRC_MODE_SINGLE:
mLrcArea = (LinearLayout) View.inflate(context, R.layout.lrcbody_single, null);
addView(mLrcArea);
break;
case LRC_MODE_MULTIPLE:
mLrcArea = (LinearLayout) View.inflate(context, R.layout.lrcbody_multiple, null);
addView(mLrcArea);
break;
default:
throw new IllegalArgumentException();
}
mLrcMode = lrcMode;
// if lyrics is not valid then display error message
if (null == mLrc) {
TextView tv = (TextView) View.inflate(context, R.layout.lrcsentence_single, null);
tv.setText(LyricsBody.getLastLrcErrorMsg());
tv.setTextColor(getResources().getColor(R.color.normal_sentence));
mLrcArea.addView(tv);
return false;
}
// the LyricsBody object {mLrc} has been successfully loaded and parsed
switch (lrcMode) {
case LRC_MODE_SINGLE: // only one line of lyric sentence is displayed.
mCurrIndex = mLrc.getCurrentIndex(duration);
TextView tv_single = (TextView) View.inflate(context, R.layout.lrcsentence_single, null);
tv_single.setText(mLrc.getSentenceText(mCurrIndex));
mLrcArea.addView(tv_single, 0);
break;
case LRC_MODE_MULTIPLE:
mCurrIndex = -1;
int cnt = mLrc.getSentenceCnt();
for (int k = 0; k < cnt; k++) {
TextView tv = (TextView) View.inflate(context, R.layout.lrcsentence, null);
tv.setText(mLrc.getSentenceText(k));
mLrcArea.addView(tv, k); // add a child view for one sentence at position <k>
}
break;
default:
throw new IllegalArgumentException();
};
return true;
}
/*
* under multiple-line mode, update the content of mLrcArea so that it can get correct child
* views' layout height.
* should be called asynchronously, normally by sending a message and dealing with handler.
*/
public void update() {
resetLrcState();
if (null == mLrc) {
return;
}
switch (mLrcMode) {
case LRC_MODE_MULTIPLE:
int cnt = mLrc.getSentenceCnt();
mLyricPos = new int[cnt];
mLyricPos[0] = 0; // the first sentence's position should be ZERO
mLyricHeight = new int[cnt];
int totalHeight = 0;
for (int j = 0; j < cnt; j++) {
TextView t = (TextView) mLrcArea.getChildAt(j);
int height = t.getHeight();
mLyricHeight[j] = height;
totalHeight += height;
if (j < cnt - 1) {
mLyricPos[j + 1] = totalHeight;
}
}
break;
case LRC_MODE_SINGLE:
break;
default:
break;
};
}
/*
* scroll the ScrollLrcView to its intended position.
*
* [in]context: the application context.
* [in]currMillisec: the current duration of the music track being played. in millisecond.
*
* there's 2 modes of lyrics displaying: single line mode / multiple line
*/
public void scrollLyrics(Context context, int currMillisec) {
// avoid invalid lyric state
// 20110516 evening
if (!isValidLrcState()) {
return;
}
int destIndex = mLrc.getCurrentIndex(currMillisec);
switch (mLrcMode) {
case LRC_MODE_SINGLE:
{
if (mCurrIndex != destIndex && destIndex >= 0) {
TextView tv_single = (TextView) mLrcArea.getChildAt(0); // only one TextView
tv_single.setText(mLrc.getSentenceText(destIndex));
// the animation
Animation a = new TranslateAnimation(Animation.ABSOLUTE, 0.0f,
Animation.ABSOLUTE, 0.0f,
Animation.ABSOLUTE, (float)tv_single.getHeight(),
Animation.ABSOLUTE, 0.0f);
a.setDuration(500);
a.setRepeatCount(0);
a.setInterpolator(AnimationUtils.loadInterpolator(context,
android.R.anim.decelerate_interpolator));
tv_single.startAnimation(a);
mCurrIndex = destIndex; // has been scrolled
}
break;
} // case LRC_MODE_SINGLE
case LRC_MODE_MULTIPLE:
{
// avoiding illegal array operation, 20110516 evening
if (destIndex < 0 || destIndex > mLyricPos.length - 1) {
return;
}
int destPos = mLyricPos[destIndex]; // the position it needs to scroll to
// the text color state should be changed
if (mCurrIndex != destIndex && destIndex >= 0) {
TextView tv = (TextView) mLrcArea.getChildAt(destIndex);
tv.setTextColor(getResources().getColor(R.color.highlight_sentence)); // lrccolor.xml @ /values
// the current one, restore the default color
if (mCurrIndex >= 0) {
tv = (TextView) mLrcArea.getChildAt(mCurrIndex);
tv.setTextColor(getResources().getColor(R.color.normal_sentence)); // lrccolor.xml @ /values
}
mCurrIndex = destIndex; // has been scrolled
}
int destScroll = destPos; // the real position where the scrollView should scrolls to
// (1)
int delta;
float ratio = (float)(currMillisec - mLrc.getSentenceTime(destIndex));
if (LyricsBody.INTERVAL_LAST_SENTENCE == mLrc.getIntervalToNext(destIndex)) {
delta = 0;
} else {
ratio /= (float)mLrc.getIntervalToNext(destIndex);
delta = (int)( ratio * (float)mLyricHeight[destIndex] );
}
// (2)
destScroll += delta;
// (3)
if (destScroll < getHeight() / 2) {
destScroll = 0;
}
else {
destScroll -= getHeight() / 2;
}
// (4)
scrollTo(getScrollX(), destScroll);
break;
} // case LRC_MODE_MULTIPLE
default:
break;
};
}
public void resetHighlight() {
if (!isValidLrcState()) {
return;
}
if (mLrcMode == LRC_MODE_MULTIPLE) {
for (int i = 0; i < mLrc.getSentenceCnt(); i++) {
TextView tv = (TextView) mLrcArea.getChildAt(i);
tv.setTextColor(getResources().getColor(R.color.normal_sentence));
}
}
}
public int singleLineModeHeightPixel() {
switch (mScrDensity) {
case DisplayMetrics.DENSITY_HIGH:
return 66;
case DisplayMetrics.DENSITY_MEDIUM:
return 44;
case DisplayMetrics.DENSITY_LOW:
return 33;
default:
return 44;
}
}
public int singleLineModePaddingPixel() {
switch (mScrDensity) {
case DisplayMetrics.DENSITY_HIGH:
return 15;
case DisplayMetrics.DENSITY_MEDIUM:
return 10;
case DisplayMetrics.DENSITY_LOW:
return 7;
default:
return 10;
}
}
/*
* getters
*/
public int getLrcListHeight() {
return mLyricPos[mLyricPos.length - 2];
}
private void resetLrcState() {
mLyricPos = null;
mLyricHeight = null;
mCurrIndex = -1;
}
private boolean isValidLrcState() {
if (mLrc != null) {
if (mLrcMode == LRC_MODE_MULTIPLE) {
return (mLyricPos != null && mLyricHeight != null);
}
return true;
}
return false;
}
}