ex
Fork of mbed-os-example-mbed5-blinky by
Diff: dcs-sdk-java-master/app/src/main/java/com/baidu/duer/dcs/devicemodule/audioplayer/AudioPlayerDeviceModule.java
- Revision:
- 45:2aa9f933c8d2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dcs-sdk-java-master/app/src/main/java/com/baidu/duer/dcs/devicemodule/audioplayer/AudioPlayerDeviceModule.java Tue Jul 18 16:34:48 2017 +0800
@@ -0,0 +1,516 @@
+/*
+ * Copyright (c) 2017 Baidu, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baidu.duer.dcs.devicemodule.audioplayer;
+
+import com.baidu.duer.dcs.devicemodule.audioplayer.message.ClearQueuePayload;
+import com.baidu.duer.dcs.devicemodule.audioplayer.message.PlayPayload;
+import com.baidu.duer.dcs.devicemodule.audioplayer.message.PlaybackStatePayload;
+import com.baidu.duer.dcs.devicemodule.audioplayer.message.StopPayload;
+import com.baidu.duer.dcs.devicemodule.audioplayer.report.AudioPlayStateReport;
+import com.baidu.duer.dcs.devicemodule.audioplayer.report.AudioPlayerProgressReporter;
+import com.baidu.duer.dcs.devicemodule.audioplayer.report.AudioPlayerTimer;
+import com.baidu.duer.dcs.devicemodule.system.HandleDirectiveException;
+import com.baidu.duer.dcs.framework.BaseDeviceModule;
+import com.baidu.duer.dcs.framework.IMessageSender;
+import com.baidu.duer.dcs.framework.message.ClientContext;
+import com.baidu.duer.dcs.framework.message.Directive;
+import com.baidu.duer.dcs.framework.message.Header;
+import com.baidu.duer.dcs.systeminterface.IMediaPlayer;
+import com.baidu.duer.dcs.util.LogUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * 音乐播放的端能力实现,处理指令:Play,Stop,ClearQueue
+ * <p>
+ * Created by guxiuzhong@baidu.com on 2017/5/31.
+ */
+public class AudioPlayerDeviceModule extends BaseDeviceModule {
+ private static final String TAG = AudioPlayerDeviceModule.class.getSimpleName();
+ // 播放列表,先进先出
+ private LinkedList<PlayPayload.Stream> playQueue = new LinkedList<>();
+ // 当前stream的token
+ private String latestStreamToken = "";
+ // 当前manager的播放器
+ private IMediaPlayer mediaPlayer;
+ // 播放上报
+ private AudioPlayStateReport audioPlayStateReport;
+ // 开始时的缓冲时间
+ private long bufferingStartMilliseconds;
+ // 结束时的缓冲时间
+ private long bufferingEndMilliseconds;
+ // 用来统计状态的时间间隔offsetInMilliseconds
+ private AudioPlayerTimer timer;
+ // 播放状态的上报eg:暂停,完成等
+ private AudioPlayerProgressReporter progressReporter;
+ // 回调接口
+ private List<IMediaPlayer.IMediaPlayerListener> audioPlayerListeners;
+
+ public AudioPlayerDeviceModule(IMediaPlayer mediaPlayer,
+ IMessageSender messageSender) {
+ super(ApiConstants.NAMESPACE, messageSender);
+ this.mediaPlayer = mediaPlayer;
+ this.mediaPlayer.addMediaPlayerListener(mediaPlayerListener);
+ this.audioPlayStateReport = new AudioPlayStateReport(getNameSpace(),
+ messageSender,
+ audioPlayStateReportListener);
+ this.timer = new AudioPlayerTimer();
+ this.progressReporter = new AudioPlayerProgressReporter(
+ new ProgressReportDelayEventRunnable(audioPlayStateReport),
+ new ProgressReportIntervalEventRunnable(audioPlayStateReport), timer);
+ this.audioPlayerListeners = Collections.synchronizedList(
+ new ArrayList<IMediaPlayer.IMediaPlayerListener>());
+ }
+
+ @Override
+ public ClientContext clientContext() {
+ String namespace = ApiConstants.NAMESPACE;
+ String name = ApiConstants.Events.PlaybackState.NAME;
+ Header header = new Header(namespace, name);
+ PlaybackStatePayload payload = new PlaybackStatePayload(latestStreamToken,
+ mediaPlayer.getCurrentPosition(),
+ audioPlayStateReport.getState().name());
+ return new ClientContext(header, payload);
+ }
+
+ @Override
+ public void handleDirective(Directive directive) throws HandleDirectiveException {
+ LogUtil.d(TAG, "dcs-play-directive:" + directive.rawMessage);
+ String directiveName = directive.getName();
+ LogUtil.d(TAG, "dcs-play-directiveName:" + directiveName);
+ if (ApiConstants.Directives.Play.NAME.equals(directiveName)) {
+ handlePlay((PlayPayload) directive.getPayload());
+ } else if (ApiConstants.Directives.Stop.NAME.equals(directiveName)) {
+ handleStop((StopPayload) directive.getPayload());
+ } else if (ApiConstants.Directives.ClearQueue.NAME.equals(directiveName)) {
+ handleClearQueue((ClearQueuePayload) directive.getPayload());
+ } else {
+ String message = "audioPlayer cannot handle the directive";
+ throw (new HandleDirectiveException(
+ HandleDirectiveException.ExceptionType.UNSUPPORTED_OPERATION, message));
+ }
+ }
+
+ /**
+ * 处理播放指令(Play)
+ *
+ * @param payload payload
+ */
+ private void handlePlay(PlayPayload payload) {
+ PlayPayload.AudioItem item = payload.audioItem;
+ if (payload.playBehavior == PlayPayload.PlayBehavior.REPLACE_ALL) {
+ clearAll();
+ } else if (payload.playBehavior == PlayPayload.PlayBehavior.REPLACE_ENQUEUED) {
+ clearEnqueued();
+ }
+ final PlayPayload.Stream stream = item.stream;
+ String streamUrl = stream.url;
+ String streamId = stream.token;
+ long offset = stream.offsetInMilliseconds;
+ LogUtil.i(TAG, "URL:" + streamUrl);
+ LogUtil.i(TAG, "StreamId:" + streamId);
+ LogUtil.i(TAG, "Offset:" + offset);
+ add(stream);
+ }
+
+ /**
+ * 处理停止指令(Stop)
+ *
+ * @param payload payload
+ */
+ private void handleStop(StopPayload payload) {
+ stop();
+ }
+
+ /**
+ * 处理清空队列指令(Stop)
+ *
+ * @param clearQueuePayload clearQueuePayload
+ */
+ private void handleClearQueue(ClearQueuePayload clearQueuePayload) {
+ // 清除播放列表,并停止当前播放的音频(如果有)
+ if (clearQueuePayload.clearBehavior == ClearQueuePayload.ClearBehavior.CLEAR_ALL) {
+ audioPlayStateReport.clearQueueAll();
+ clearAll();
+ } else if (clearQueuePayload.clearBehavior == ClearQueuePayload.ClearBehavior.CLEAR_ENQUEUED) {
+ // 清除播放列表,但不影响当前播放
+ audioPlayStateReport.clearQueueEnqueued();
+ clearEnqueued();
+ }
+ }
+
+ private void add(PlayPayload.Stream stream) {
+ String expectedPreviousToken = stream.expectedPreviousToken;
+ boolean startPlaying = playQueue.isEmpty();
+ if (expectedPreviousToken == null || latestStreamToken.isEmpty()
+ || latestStreamToken.equals(expectedPreviousToken)) {
+ playQueue.add(stream);
+ }
+ LogUtil.d(TAG, " coming playQueue size :" + playQueue.size());
+ if (startPlaying) {
+ startPlay();
+ }
+ }
+
+ /**
+ * 开始播放音乐
+ */
+ private void startPlay() {
+ if (playQueue.isEmpty()) {
+ LogUtil.d(TAG, "startPlay-playQueue isEmpty !!");
+ return;
+ }
+ PlayPayload.Stream currentStream = playQueue.peek();
+ if (currentStream == null) {
+ return;
+ }
+ latestStreamToken = currentStream.token;
+ String url = currentStream.url;
+ // 从哪个位置开始播放
+ long offset = currentStream.offsetInMilliseconds;
+ // 判断是否是流类型还是URL类型
+ if (currentStream.hasAttachedContent()) {
+ mediaPlayer.play(new IMediaPlayer.MediaResource(currentStream.getAttachedContent()));
+ } else {
+ mediaPlayer.play(new IMediaPlayer.MediaResource(url));
+ }
+ mediaPlayer.seekTo((int) offset);
+ }
+
+ private IMediaPlayer.IMediaPlayerListener mediaPlayerListener = new IMediaPlayer.SimpleMediaPlayerListener() {
+ // 是否处于暂停
+ private boolean isPause;
+ // 是否第一次到达了100
+ private boolean stutterFinished;
+ // 是否处于上报ProgressReport事件中
+ private boolean progressReporting;
+ // 是否处于缓冲中
+ private boolean bufferUnderRunInProgress;
+
+ @Override
+ public void onInit() {
+ super.onInit();
+ LogUtil.d(TAG, "onInit");
+ isPause = false;
+ stutterFinished = false;
+ progressReporting = false;
+ bufferUnderRunInProgress = false;
+ }
+
+ @Override
+ public void onPrepared() {
+ super.onPrepared();
+ LogUtil.d(TAG, "onPrepared");
+ fireOnPrepared();
+ }
+
+ @Override
+ public void onPlaying() {
+ super.onPlaying();
+ LogUtil.d(TAG, "onPlaying");
+ // 暂停后继续播放
+ if (isPause) {
+ isPause = false;
+ audioPlayStateReport.playbackResumed();
+ } else {
+ // 第一次播放
+ PlayPayload.Stream stream = playQueue.peek();
+ if (stream == null) {
+ return;
+ }
+ long offset = stream.offsetInMilliseconds;
+ LogUtil.d(TAG, "onPlaying---Duration----:" + mediaPlayer.getDuration());
+ timer.reset(offset, mediaPlayer.getDuration());
+
+ // 上报PlaybackStarted事件
+ audioPlayStateReport.playbackStarted();
+ }
+ startTimerAndProgressReporter();
+ fireOnPlaying();
+ }
+
+ @Override
+ public void onPaused() {
+ LogUtil.d(TAG, "onPaused");
+ stopTimerAndProgressReporter();
+ isPause = true;
+ audioPlayStateReport.playbackPaused();
+ fireOnPaused();
+ }
+
+ @Override
+ public void onStopped() {
+ super.onStopped();
+ stopTimerAndProgressReporter();
+ audioPlayStateReport.playbackStopped();
+ fireOnStopped();
+ }
+
+ @Override
+ public void onCompletion() {
+ LogUtil.d(TAG, "onCompletion");
+ stopTimerAndProgressReporter();
+ playQueue.poll();
+ audioPlayStateReport.playbackFinished();
+ audioPlayStateReport.playbackNearlyFinished();
+ if (!playQueue.isEmpty()) {
+ startPlay();
+ }
+ fireOnCompletion();
+ }
+
+ @Override
+ public void onRelease() {
+ LogUtil.d(TAG, "onError");
+ stopTimerAndProgressReporter();
+ fireOnRelease();
+ }
+
+ @Override
+ public void onError(String error, IMediaPlayer.ErrorType errorType) {
+ LogUtil.d(TAG, "onError");
+ playQueue.clear();
+ audioPlayStateReport.playbackFailed(errorType);
+ stopTimerAndProgressReporter();
+ fireOnError(error, errorType);
+ }
+
+ @Override
+ public void onBufferingUpdate(int percent) {
+ LogUtil.d(TAG, "onBufferingUpdate:" + percent);
+ fireOnBufferingUpdate(percent);
+ PlayPayload.Stream stream = playQueue.peek();
+ if (stream == null) {
+ return;
+ }
+ // Play指令有progressReportDelayInMilliseconds
+ if (!progressReporting && stream.getProgressReportRequired()) {
+ LogUtil.d(TAG, "onBufferingUpdate:" + percent);
+ progressReporting = true;
+ progressReporter.disable();
+ progressReporter.setup(stream.progressReport);
+ long offset = stream.offsetInMilliseconds;
+ timer.reset(offset, mediaPlayer.getDuration());
+ startTimerAndProgressReporter();
+ }
+
+ // 已经缓冲完成了
+ if (stutterFinished) {
+ return;
+ }
+ // 开始缓冲
+ if (!bufferUnderRunInProgress) {
+ LogUtil.d(TAG, "==playbackStutterStarted");
+ bufferUnderRunInProgress = true;
+ bufferingStartMilliseconds = System.currentTimeMillis();
+ audioPlayStateReport.playbackStutterStarted();
+ }
+ // 缓冲完毕后上报playbackStutterFinished
+ if (percent >= 100) {
+ stutterFinished = true;
+ bufferingEndMilliseconds = System.currentTimeMillis();
+ audioPlayStateReport.playbackStutterFinished();
+ }
+ }
+ };
+
+ private void startTimerAndProgressReporter() {
+ timer.start();
+ if (progressReporter.isSetup()) {
+ progressReporter.start();
+ }
+ }
+
+ private void stopTimerAndProgressReporter() {
+ timer.stop();
+ progressReporter.stop();
+ }
+
+ private void clearAll() {
+ stop();
+ playQueue.clear();
+ }
+
+ private void clearEnqueued() {
+ PlayPayload.Stream top = playQueue.poll();
+ playQueue.clear();
+ if (top != null) {
+ playQueue.add(top);
+ }
+ }
+
+ private void stop() {
+ if (!playQueue.isEmpty() && isPlayingOrPaused()) {
+ stopTimerAndProgressReporter();
+ // 要把播放策略中的对应的那一条删除了
+ mediaPlayer.stop();
+ }
+ }
+
+ private boolean isPlaying() {
+ return (audioPlayStateReport.getState() == AudioPlayStateReport.AudioPlayerState.PLAYING
+ || audioPlayStateReport.getState() == AudioPlayStateReport.AudioPlayerState.PAUSED
+ || audioPlayStateReport.getState() == AudioPlayStateReport.AudioPlayerState.BUFFER_UNDERRUN);
+ }
+
+ private boolean isPlayingOrPaused() {
+ return isPlaying() || audioPlayStateReport.getState() == AudioPlayStateReport.AudioPlayerState.PAUSED;
+ }
+
+ @Override
+ public void release() {
+ if (mediaPlayer != null) {
+ mediaPlayer.release();
+ mediaPlayer.removeMediaPlayerListener(mediaPlayerListener);
+ }
+ stopTimerAndProgressReporter();
+ audioPlayerListeners.clear();
+ }
+
+ /**
+ * 播放上报时需要的信息
+ */
+ private AudioPlayStateReport.AudioPlayStateReportListener audioPlayStateReportListener =
+ new AudioPlayStateReport.AudioPlayStateReportListener() {
+ @Override
+ public String getCurrentStreamToken() {
+ return latestStreamToken;
+ }
+
+ @Override
+ public long getCurrentOffsetInMilliseconds() {
+ return getCurrentOffsetInMillisecondsByTime();
+ }
+
+ @Override
+ public long getStutterDurationInMilliseconds() {
+ // 缓冲时间ms
+ return bufferingEndMilliseconds - bufferingStartMilliseconds;
+ }
+ };
+
+ private long getCurrentOffsetInMillisecondsByTime() {
+ AudioPlayStateReport.AudioPlayerState playerActivity = audioPlayStateReport.getState();
+ long offset;
+ switch (playerActivity) {
+ case PLAYING:
+ case PAUSED:
+ case BUFFER_UNDERRUN:
+ case STOPPED:
+ case FINISHED:
+ offset = timer.getOffsetInMilliseconds();
+ break;
+ case IDLE:
+ default:
+ offset = 0;
+ }
+ LogUtil.d(TAG, "getCurrentOffsetInMilliseconds offset:" + offset);
+ return offset;
+ }
+
+ private static class ProgressReportDelayEventRunnable implements Runnable {
+ private final AudioPlayStateReport audioPlayStateReport;
+
+ ProgressReportDelayEventRunnable(AudioPlayStateReport audioPlayStateReport) {
+ this.audioPlayStateReport = audioPlayStateReport;
+ }
+
+ @Override
+ public void run() {
+ audioPlayStateReport.reportProgressDelay();
+ }
+ }
+
+ private static class ProgressReportIntervalEventRunnable implements Runnable {
+ private final AudioPlayStateReport audioPlayStateReport;
+
+ ProgressReportIntervalEventRunnable(AudioPlayStateReport audioPlayStateReport) {
+ this.audioPlayStateReport = audioPlayStateReport;
+ }
+
+ @Override
+ public void run() {
+ audioPlayStateReport.reportProgressInterval();
+ }
+ }
+
+ private void fireOnPrepared() {
+ for (IMediaPlayer.IMediaPlayerListener listener : audioPlayerListeners) {
+ listener.onPrepared();
+ }
+ }
+
+ private void fireOnRelease() {
+ for (IMediaPlayer.IMediaPlayerListener listener : audioPlayerListeners) {
+ listener.onRelease();
+ }
+ }
+
+ private void fireOnPlaying() {
+ for (IMediaPlayer.IMediaPlayerListener listener : audioPlayerListeners) {
+ listener.onPlaying();
+ }
+ }
+
+ private void fireOnPaused() {
+ for (IMediaPlayer.IMediaPlayerListener listener : audioPlayerListeners) {
+ listener.onPaused();
+ }
+ }
+
+ private void fireOnStopped() {
+ for (IMediaPlayer.IMediaPlayerListener listener : audioPlayerListeners) {
+ listener.onStopped();
+ }
+ }
+
+ private void fireOnCompletion() {
+ for (IMediaPlayer.IMediaPlayerListener listener : audioPlayerListeners) {
+ listener.onCompletion();
+ }
+ }
+
+ private void fireOnError(String error, IMediaPlayer.ErrorType errorType) {
+ for (IMediaPlayer.IMediaPlayerListener listener : audioPlayerListeners) {
+ listener.onError(error, errorType);
+ }
+ }
+
+ private void fireOnBufferingUpdate(int percent) {
+ for (IMediaPlayer.IMediaPlayerListener listener : audioPlayerListeners) {
+ listener.onBufferingUpdate(percent);
+ }
+ }
+
+ private void fireBufferingStart() {
+ for (IMediaPlayer.IMediaPlayerListener listener : audioPlayerListeners) {
+ listener.onBufferingStart();
+ }
+ }
+
+ private void fireBufferingEnd() {
+ for (IMediaPlayer.IMediaPlayerListener listener : audioPlayerListeners) {
+ listener.onBufferingEnd();
+ }
+ }
+
+ public void addAudioPlayListener(IMediaPlayer.IMediaPlayerListener listener) {
+ audioPlayerListeners.add(listener);
+ }
+}
