12
12
import android .os .Handler ;
13
13
import android .os .Looper ;
14
14
import android .util .AttributeSet ;
15
+ import android .util .Log ;
15
16
import android .util .TypedValue ;
16
17
import android .view .View ;
17
18
18
19
import androidx .annotation .Nullable ;
19
20
import androidx .annotation .RequiresApi ;
20
21
21
- import java .util .ArrayList ;
22
+ import java .util .Iterator ;
23
+ import java .util .LinkedHashMap ;
22
24
import java .util .List ;
23
25
24
26
import io .agora .lrcview .bean .LrcData ;
@@ -48,15 +50,15 @@ public class PitchView extends View {
48
50
49
51
// 完成 PitchView.OnActionListener#onOriginalPitch的需求
50
52
// 当前 Pitch 所在的字的开始时间
51
- private long currentPitchStartTime = 0 ;
53
+ private long currentPitchStartTime = - 1 ;
52
54
// 当前 Pitch 所在的字的结束时间
53
- private long currentPitchEndTime = 0 ;
55
+ private long currentPitchEndTime = - 1 ;
54
56
// 当前 Pitch 所在的句的结束时间
55
- private long currentEntryEndTime = 0 ;
56
- // 当前在打分的所在句的结束时间
57
- private long currentScoreEntryEndTime = 0 ;
57
+ private long currentEntryEndTime = -1 ;
58
58
// 当前在打分的所在句的结束时间
59
59
private long lrcEndTime = 0 ;
60
+ // 当前 时间的 Pitch
61
+ private float currentPitch = 0f ;
60
62
61
63
// 音调指示器的半径
62
64
private int indicatorRadius ;
@@ -65,7 +67,7 @@ public class PitchView extends View {
65
67
// 初始分数
66
68
private float mInitialScore ;
67
69
// 每句歌词分数
68
- public List < Double > sentenceScoreList = new ArrayList <>();
70
+ public LinkedHashMap < Long , Double > everyPitchList = new LinkedHashMap <>();
69
71
// 累计分数
70
72
public float cumulatedScore ;
71
73
// 歌曲总分数
@@ -152,6 +154,7 @@ protected void onDraw(Canvas canvas) {
152
154
}
153
155
//</editor-fold>
154
156
157
+ //<editor-fold desc="Draw Related">
155
158
private void drawLocalPitch (Canvas canvas ) {
156
159
mPaint .setShader (null );
157
160
mPaint .setColor (mNormalTextColor );
@@ -233,6 +236,7 @@ private void drawItems(Canvas canvas) {
233
236
}
234
237
}
235
238
}
239
+ //</editor-fold>
236
240
237
241
/**
238
242
* 设置歌词信息
@@ -247,11 +251,14 @@ public void setLrcData(@Nullable LrcData data) {
247
251
pitchMax = 0 ;
248
252
pitchMin = 100 ;
249
253
250
- currentPitchStartTime = 0 ;
251
- currentPitchEndTime = 0 ;
252
- currentEntryEndTime = 0 ;
253
- currentScoreEntryEndTime = 0 ;
254
- sentenceScoreList .clear ();
254
+ currentPitchStartTime = -1 ;
255
+ currentPitchEndTime = -1 ;
256
+ currentEntryEndTime = -1 ;
257
+ everyPitchList .clear ();
258
+
259
+
260
+ cumulatedScore = mInitialScore ;
261
+ totalScore = 0 ;
255
262
256
263
if (lrcData != null && lrcData .entrys != null && !lrcData .entrys .isEmpty ()) {
257
264
@@ -267,6 +274,7 @@ public void setLrcData(@Nullable LrcData data) {
267
274
}
268
275
}
269
276
277
+
270
278
invalidate ();
271
279
}
272
280
@@ -279,22 +287,25 @@ private void setMLocalPitch(float mLocalPitch) {
279
287
}
280
288
281
289
/**
282
- * 根据当前播放时间获取 Pitch
290
+ * 根据当前播放时间获取 Pitch,并且更新
291
+ * {@link this#currentPitchStartTime}
292
+ * {@link this#currentPitchEndTime}
293
+ * {@link this#currentEntryEndTime}
283
294
*
284
295
* @return 当前时间歌词的 Pitch
285
296
*/
286
- private float findPitchByTime () {
297
+ private float findPitchByTime (long time ) {
287
298
if (lrcData == null ) return 0 ;
288
299
289
300
float resPitch = 0 ;
290
301
int entryCount = lrcData .entrys .size ();
291
302
for (int i = 0 ; i < entryCount ; i ++) {
292
303
LrcEntryData tempEntry = lrcData .entrys .get (i );
293
- if (mCurrentTime >= tempEntry .getStartTime ()) { // 索引
304
+ if (time >= tempEntry .getStartTime () && time <= tempEntry . getEndTime ()) { // 索引
294
305
int toneCount = tempEntry .tones .size ();
295
306
for (int j = 0 ; j < toneCount ; j ++) {
296
307
LrcEntryData .Tone tempTone = tempEntry .tones .get (j );
297
- if (mCurrentTime <= tempTone .end ) {
308
+ if (time >= tempTone . begin && time <= tempTone .end ) {
298
309
resPitch = tempTone .pitch ;
299
310
currentPitchStartTime = tempTone .begin ;
300
311
currentPitchEndTime = tempTone .end ;
@@ -307,10 +318,16 @@ private float findPitchByTime() {
307
318
}
308
319
}
309
320
if (resPitch == 0 ) {
310
- currentPitchStartTime = 0 ;
311
- currentPitchEndTime = 0 ;
312
- currentEntryEndTime = 0 ;
321
+ currentPitchStartTime = -1 ;
322
+ currentPitchEndTime = -1 ;
323
+ if (time > currentEntryEndTime )
324
+ currentEntryEndTime = -1 ;
325
+ }else {
326
+ // 进入此行代码条件 : 所唱歌词句开始时间 <= 当前时间 >= 所唱歌词句结束时间
327
+ // 强行加上一个 0 分 ,标识此为可打分句
328
+ everyPitchList .put (time , 0d );
313
329
}
330
+ currentPitch = resPitch ;
314
331
return resPitch ;
315
332
}
316
333
@@ -321,9 +338,9 @@ private float findPitchByTime() {
321
338
*/
322
339
public void updateLocalPitch (float pitch ) {
323
340
if (lrcData == null ) return ;
324
- float desiredPitch = findPitchByTime () ;
325
- if (desiredPitch != 0 )
326
- updateScore ( pitchToTone (pitch ), pitchToTone (desiredPitch ));
341
+ float tempPitch = currentPitch ;
342
+ if (tempPitch != 0 )
343
+ calculateScore ( mCurrentTime , pitchToTone (pitch ), pitchToTone (tempPitch ));
327
344
328
345
mHandler .removeCallbacksAndMessages (null );
329
346
mHandler .postDelayed (() -> {
@@ -334,40 +351,60 @@ public void updateLocalPitch(float pitch) {
334
351
ObjectAnimator .ofFloat (this , "mLocalPitch" , this .mLocalPitch , pitch ).setDuration (50 ).start ();
335
352
}
336
353
337
- /**
338
- * 更新当前分数
339
- *
340
- * @param currentTone 演唱值
341
- * @param desiredTone 理想值
342
- */
343
- private void updateScore (double currentTone , double desiredTone ) {
354
+ private void calculateScore (long currentTime , double currentTone , double desiredTone ) {
344
355
double score = 1 - Math .abs (desiredTone - currentTone ) / desiredTone ;
356
+ // 得分线以下的分数归零
345
357
score = score >= scoreCountLine ? score : 0f ;
358
+ // 百分制分数 * 每句固定分数
346
359
score *= scorePerSentence ;
360
+ everyPitchList .put (currentTime , score );
361
+ }
347
362
348
- // 当前未在打分 <==> 定位打分句结束时间到当前句
349
- if (sentenceScoreList .isEmpty ()) currentScoreEntryEndTime = currentEntryEndTime ;
350
-
351
- // 打分句结束时间已过 或者 最后一句已经结束
352
- if (mCurrentTime > currentScoreEntryEndTime || mCurrentTime > lrcEndTime ) { // 已经到下一句了
353
- // 分数列表不为空
354
- if (!sentenceScoreList .isEmpty ()) {
355
-
363
+ /**
364
+ * 更新当前分数
365
+ *
366
+ * @param time 当前歌曲播放时间 毫秒
367
+ */
368
+ private void updateScore (long time ) {
369
+ // 没有开始 || 在空档期
370
+ boolean pushAll = currentEntryEndTime == -1 ;
371
+ // 当前时间 >= 打分句结束时间
372
+ boolean isThisSentenceOver = time >= currentEntryEndTime ;
373
+ // 当前时间 >= 歌词结束时间
374
+ boolean isThisSongOver = time >= lrcEndTime ;
375
+
376
+ if (pushAll || isThisSentenceOver || isThisSongOver ) {
377
+ if (!everyPitchList .isEmpty ()) {
356
378
// 计算歌词当前句的分数 = 所有打分/分数个数
357
- double tempScore = 0 ;
358
- for (Double toneScore : sentenceScoreList )
359
- tempScore += toneScore ;
379
+ double tempTotalScore = 0 ;
380
+ int scoreCount = 0 ;
381
+
382
+ Double tempScore ;
383
+ // 两种情况 1. 到了空档期 2. 到了下一句
384
+ Iterator <Long > iterator = everyPitchList .keySet ().iterator ();
385
+ while (iterator .hasNext ()){
386
+ Long duration = iterator .next ();
387
+ if (pushAll || duration <= currentEntryEndTime ) {
388
+ tempScore = everyPitchList .get (duration );
389
+ iterator .remove ();
390
+ everyPitchList .remove (duration );
391
+ if (tempScore != null ) {
392
+ tempTotalScore += tempScore .floatValue ();
393
+ scoreCount ++;
394
+ }
395
+ }
396
+ }
360
397
398
+ // 获取歌词pitch时为了标记可打分句给每句加了1个0分
399
+ scoreCount = Math .max (1 , scoreCount - 1 );
400
+
401
+ double scoreThisTime = tempTotalScore / scoreCount ;
361
402
// 统计到累计分数
362
- cumulatedScore += tempScore / sentenceScoreList . size () ;
403
+ cumulatedScore += scoreThisTime ;
363
404
// 回调到上层
364
- dispatchScore (score );
365
- // 清除打分
366
- sentenceScoreList .clear ();
405
+ dispatchScore (scoreThisTime );
367
406
}
368
407
}
369
-
370
- sentenceScoreList .add (score );
371
408
}
372
409
373
410
/**
@@ -387,13 +424,15 @@ private void dispatchScore(double score) {
387
424
* @param time 当前播放时间,毫秒
388
425
*/
389
426
public void updateTime (long time ) {
390
- if (lrcData == null ) {
391
- return ;
392
- } else if (time < currentPitchStartTime || time > currentPitchEndTime ) {
393
- onActionListener .onOriginalPitch (findPitchByTime (), totalPitch );
394
- }
395
-
427
+ if (lrcData == null ) return ;
396
428
this .mCurrentTime = time ;
429
+ if (time < currentPitchStartTime || time > currentPitchEndTime ) {
430
+ float tempPitch = findPitchByTime (time );
431
+ if (tempPitch > 0 ) {
432
+ onActionListener .onOriginalPitch (tempPitch , totalPitch );
433
+ }
434
+ }
435
+ updateScore (time );
397
436
398
437
invalidate ();
399
438
}
@@ -413,8 +452,9 @@ public static interface OnActionListener {
413
452
414
453
/**
415
454
* 咪咕歌词原始参考pitch值回调, 用于开发者自行实现打分逻辑. 歌词每个tone回调一次
416
- * pitch: 当前tone的pitch值
417
- * totalCount: 整个xml的tone个数, 用于开发者方便自己在app层计算平均分.
455
+ *
456
+ * @param pitch 当前tone的pitch值
457
+ * @param totalCount 整个xml的tone个数, 用于开发者方便自己在app层计算平均分.
418
458
*/
419
459
void onOriginalPitch (float pitch , int totalCount );
420
460
0 commit comments