001/* 002 * Copyright (C) 2006 The Android Open Source Project 003 * Copyright (C) 2013 Zhang Rui <bbcallen@gmail.com> 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package com.baidu.cloud.media.player; 019 020import android.annotation.SuppressLint; 021import android.annotation.TargetApi; 022import android.content.ContentResolver; 023import android.content.Context; 024import android.content.SharedPreferences; 025import android.content.res.AssetFileDescriptor; 026import android.graphics.Rect; 027import android.graphics.SurfaceTexture; 028import android.media.MediaCodecInfo; 029import android.media.MediaCodecList; 030import android.media.RingtoneManager; 031import android.net.Uri; 032import android.os.Build; 033import android.os.Bundle; 034import android.os.Handler; 035import android.os.Looper; 036import android.os.Message; 037import android.os.ParcelFileDescriptor; 038import android.os.PowerManager; 039import android.provider.Settings; 040import android.text.TextUtils; 041import android.util.Log; 042import android.view.Surface; 043import android.view.SurfaceHolder; 044 045import com.baidu.cloud.media.download.LocalHlsSec; 046import com.baidu.cloud.media.player.annotations.AccessedByNative; 047import com.baidu.cloud.media.player.annotations.CalledByNative; 048import com.baidu.cloud.media.player.apm.APMEventHandle; 049import com.baidu.cloud.media.player.misc.BDCloudTrackInfo; 050import com.baidu.cloud.media.player.misc.IMediaDataSource; 051import com.baidu.cloud.media.player.misc.ITrackInfo; 052import com.baidu.cloud.media.player.pragma.DebugLog; 053 054import org.json.JSONArray; 055import org.json.JSONObject; 056 057import java.io.FileDescriptor; 058import java.io.FileNotFoundException; 059import java.io.IOException; 060import java.lang.ref.WeakReference; 061import java.lang.reflect.Field; 062import java.security.InvalidParameterException; 063import java.text.SimpleDateFormat; 064import java.util.ArrayList; 065import java.util.Date; 066import java.util.Locale; 067import java.util.Map; 068import java.util.TimeZone; 069import java.util.Timer; 070import java.util.TimerTask; 071 072/** 073 * 播放器核心类 074 * 该类的接口与安卓系统内置的MediaPlayer类似 075 */ 076public final class BDCloudMediaPlayer extends AbstractMediaPlayer { 077 private static final String TAG = BDCloudMediaPlayer.class.getName(); 078 079 public static final String SDK_VERSION = "2.3.1"; 080 private static String mAK = ""; 081 private static String sCuid = ""; 082 private static String playId = ""; 083 private static boolean sEnableP2P = false; 084 private static boolean sEnableCache = false; 085 private static String sCacheRootPath = null; 086 087 private static final int MEDIA_NOP = 0; // interface test message 088 private static final int MEDIA_PREPARED = 1; 089 private static final int MEDIA_PLAYBACK_STOPPED = 2; 090 private static final int MEDIA_PLAYBACK_COMPLETE = 3; 091 private static final int MEDIA_BUFFERING_UPDATE = 4; 092 private static final int MEDIA_SEEK_COMPLETE = 5; 093 private static final int MEDIA_SET_VIDEO_SIZE = 6; 094 private static final int MEDIA_TIMED_TEXT = 99; 095 private static final int MEDIA_ERROR = 100; 096 private static final int MEDIA_INFO = 200; 097 098 protected static final int MEDIA_SET_VIDEO_SAR = 10001; 099 100 private static final int AVAPP_EVENT_METADATA = 0x13000; 101 private static final int AVAPP_EVENT_DNS = 0x14000; 102 private static final int AVAPP_EVENT_HTTP_CONNECT_END = 0x30002; 103 104 // ---------------------------------------- 105 // options 106 private static final int IJK_LOG_UNKNOWN = 0; 107 private static final int IJK_LOG_DEFAULT = 1; 108 109 private static final int IJK_LOG_VERBOSE = 2; 110 private static final int IJK_LOG_DEBUG = 3; 111 private static final int IJK_LOG_INFO = 4; 112 private static final int IJK_LOG_WARN = 5; 113 private static final int IJK_LOG_ERROR = 6; 114 private static final int IJK_LOG_FATAL = 7; 115 private static final int IJK_LOG_SILENT = 8; 116 117 private static final int OPT_CATEGORY_FORMAT = 1; 118 private static final int OPT_CATEGORY_CODEC = 2; 119 private static final int OPT_CATEGORY_SWS = 3; 120 private static final int OPT_CATEGORY_PLAYER = 4; 121 122 private static final int SDL_FCC_YV12 = 0x32315659; // YV12 123 private static final int SDL_FCC_RV16 = 0x36315652; // RGB565 124 private static final int SDL_FCC_RV32 = 0x32335652; // RGBX8888 125 // ---------------------------------------- 126 127 // ---------------------------------------- 128 // properties 129 private static final int PROP_FLOAT_VIDEO_DECODE_FRAMES_PER_SECOND = 10001; 130 private static final int PROP_FLOAT_VIDEO_OUTPUT_FRAMES_PER_SECOND = 10002; 131 private static final int FFP_PROP_FLOAT_PLAYBACK_RATE = 10003; 132 133 private static final int FFP_PROP_INT64_SELECTED_VIDEO_STREAM = 20001; 134 private static final int FFP_PROP_INT64_SELECTED_AUDIO_STREAM = 20002; 135 private static final int FFP_PROP_INT64_SELECTED_TIMEDTEXT_STREAM = 20011; 136 137 private static final int FFP_PROP_INT64_VIDEO_DECODER = 20003; 138 private static final int FFP_PROP_INT64_AUDIO_DECODER = 20004; 139 private static final int FFP_PROPV_DECODER_UNKNOWN = 0; 140 private static final int FFP_PROPV_DECODER_AVCODEC = 1; 141 private static final int FFP_PROPV_DECODER_MEDIACODEC = 2; 142 private static final int FFP_PROPV_DECODER_VIDEOTOOLBOX = 3; 143 private static final int FFP_PROP_INT64_VIDEO_CACHED_DURATION = 20005; 144 private static final int FFP_PROP_INT64_AUDIO_CACHED_DURATION = 20006; 145 private static final int FFP_PROP_INT64_VIDEO_CACHED_BYTES = 20007; 146 private static final int FFP_PROP_INT64_AUDIO_CACHED_BYTES = 20008; 147 private static final int FFP_PROP_INT64_VIDEO_CACHED_PACKETS = 20009; 148 private static final int FFP_PROP_INT64_AUDIO_CACHED_PACKETS = 20010; 149 private static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_BACKWARDS = 20201; 150 private static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_FORWARDS = 20202; 151 private static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_CAPACITY = 20203; 152 private static final int FFP_PROP_INT64_TRAFFIC_STATISTIC_BYTE_COUNT = 20204; 153 private static final int FFP_PROP_INT64_CACHE_STATISTIC_PHYSICAL_POS = 20205; 154 private static final int FFP_PROP_INT64_CACHE_STATISTIC_BUF_FORWARDS = 20206; 155 private static final int FFP_PROP_INT64_CACHE_STATISTIC_FILE_POS = 20207; 156 private static final int FFP_PROP_INT64_CACHE_STATISTIC_COUNT_BYTES = 20208; 157 private static final int FFP_PROP_INT64_BIT_RATE = 20100; 158 private static final int FFP_PROP_INT64_TCP_SPEED = 20200; 159 private static final int FFP_PROP_INT64_LATEST_SEEK_LOAD_DURATION = 20300; 160 // ---------------------------------------- 161 // source switch mode 162 // 普通模式,不支持无缝切换 163 public static final int SOURCE_SWITCH_NORMAL_MODE = 0; 164 // MOV 无缝模式,支持mov,mp4,m4a,3gp,3g2,mj2媒体格式 165 public static final int SOURCE_SWITCH_SMOOTH_MODE = 1; 166 // HLS无缝模式 167 public static final int SOURCE_SWITCH_SMOOTH_HLS_MODE = 2; 168 // ---------------------------------------- 169 private static final String SP_FILE_FOR_KEY = "__cyberplayer_dl_sec"; 170 171 @AccessedByNative 172 private long mNativeMediaPlayer; 173 @AccessedByNative 174 private long mNativeMediaDataSource; 175 176 @AccessedByNative 177 private long mNativeAndroidIO; 178 179 @AccessedByNative 180 private int mNativeSurfaceTexture; 181 182 @AccessedByNative 183 private int mListenerContext; 184 185 private SurfaceHolder mSurfaceHolder; 186 private EventHandler mEventHandler; 187 private PowerManager.WakeLock mWakeLock = null; 188 private boolean mScreenOnWhilePlaying; 189 private boolean mStayAwake; 190 191 private int mVideoWidth; 192 private int mVideoHeight; 193 private int mVideoSarNum; 194 private int mVideoSarDen; 195 196 private String mDataSource; 197 private int mNetworkTimeoutInUs = 15000000; 198 199 private int stayInterval = 0; 200 private long lastStartPlayTime = 0L; 201 private JSONArray bufferStatJsonArray = new JSONArray(); 202 private Date latestBufferStartTime = null; 203 private long bufferingStartPosition = -1; 204 private boolean mApmEventPlayerCreated = false; 205 206 private long startPrepareTimeForApm = 0L; 207 private int maxCacheSizeForApm = 0; 208 private int cachePauseTimeForApm = 0; 209 private int firstBufferingTimeForApm = 0; 210 private boolean toggleFrameChasingForApm; 211 private volatile boolean isEndSended = true; 212 private Timer apmPlayingTimer = null; 213 214 private Context appContext = null; 215 216 private String mCustomisedHeaders = null; 217 218 219 /** 220 * 设置Access Key 221 * 222 * @param akOfBDCloud 百度云后台的ak,详见 百度云后台 --> 右上角的用户名 --> "安全认证" --> "Access Key" 223 */ 224 public static void setAK(String akOfBDCloud) { 225 mAK = akOfBDCloud; 226 } 227 228 public static void setCuid(String cuid) { 229 sCuid = cuid; 230 APMEventHandle.setCuid(cuid); 231 } 232 233 /** 234 * 设置是否开启P2P下载模块 235 * 236 * @param isEnable 是否开启 237 */ 238 public static void setP2PEnabled(boolean isEnable) { 239 sEnableP2P = isEnable; 240 } 241 242 /** 243 * 设置是否开启本地缓存功能,开启该功能后,可以在观看过程中将音视频数据临时缓存到本地目录 244 * 245 * @param isEnable 是否开启 246 * @param cachePath 临时缓存路径,如果开启本地缓存功能,该路径不能为空 247 */ 248 public static void setLocalCacheEnabled(boolean isEnable, String cachePath) { 249 sEnableCache = isEnable; 250 sCacheRootPath = cachePath; 251 if (sEnableCache && TextUtils.isEmpty(sCacheRootPath)) { 252 throw new IllegalArgumentException("cachePath can not be empty if enabled local cache"); 253 } 254 } 255 256 /** 257 * Default library loader Load them by yourself, if your libraries are not installed at default place. 258 */ 259 private static final BDCloudLibLoader sLocalLibLoader = new BDCloudLibLoader() { 260 @Override 261 public void loadLibrary(String libName) throws UnsatisfiedLinkError, SecurityException { 262 System.loadLibrary(libName); 263 } 264 }; 265 266 private static volatile boolean mIsLibLoaded = false; 267 268 /** 269 * so库定制加载,一般不需要调用。因实例化BDCloudMediaPlayer时会自动加载so,若想定制加载,务必在创建player实例前调用。 270 * 271 * @param libLoader 272 */ 273 public static void loadLibrariesOnce(BDCloudLibLoader libLoader) { 274 synchronized (BDCloudMediaPlayer.class) { 275 if (!mIsLibLoaded) { 276 if (libLoader == null) { 277 libLoader = sLocalLibLoader; 278 } 279 280 try { 281 libLoader.loadLibrary("pcdn"); 282 } catch (Throwable e) { // In case p2p module is not enabled 283 // no need to log this info 284 // e.printStackTrace(); 285 } finally { 286 libLoader.loadLibrary("stlport_shared"); 287 libLoader.loadLibrary("bdsoundutils"); 288 libLoader.loadLibrary("bdplayer"); 289 mIsLibLoaded = true; 290 } 291 } 292 } 293 } 294 295 /** 296 * 默认构造方法 297 * <p> 298 * 当使用完BDCloudMediaPlayer之后,务必调用{@link #release()}以释放资源,否则过多的BDCloudMediaPlayer实例可能导致异常。 299 * </p> 300 */ 301 public BDCloudMediaPlayer(Context context) { 302 this(context, sLocalLibLoader); 303 } 304 305 /** 306 * 构造方法 307 * <p> 308 * 当使用完BDCloudMediaPlayer之后,务必调用{@link #release()}以释放资源,否则过多的BDCloudMediaPlayer实例可能导致异常。 309 * </p> 310 * 311 * @param context 上下文 312 * @param libLoader 定制so加载器 313 */ 314 public BDCloudMediaPlayer(Context context, BDCloudLibLoader libLoader) { 315 appContext = context.getApplicationContext(); 316 // 禁止用户行为数据上报,用户行为后台暂不维护 317 APMEventHandle.setUAUploadEnable(false); 318 APMEventHandle.setCuid(sCuid); 319 mApmEventPlayerCreated = true; 320 321 initPlayer(libLoader); 322 } 323 324 private void initPlayer(BDCloudLibLoader libLoader) { 325 loadLibrariesOnce(libLoader); 326 327 Looper looper; 328 if ((looper = Looper.myLooper()) != null) { 329 mEventHandler = new EventHandler(this, looper); 330 } else if ((looper = Looper.getMainLooper()) != null) { 331 mEventHandler = new EventHandler(this, looper); 332 } else { 333 mEventHandler = null; 334 } 335 336 /* 337 * Native init requires a weak reference to our object. It's easier to create it here than in C++. 338 */ 339 native_init(new WeakReference<BDCloudMediaPlayer>(this)); 340 341 setDecodeMode(DECODE_AUTO); 342 setOption(OPT_CATEGORY_PLAYER, "start-on-prepared", 0); 343 setOption(OPT_CATEGORY_PLAYER, "max-fps", 30); 344 setOption(OPT_CATEGORY_PLAYER, "framedrop", 1); 345 setOption(OPT_CATEGORY_PLAYER, "framechasing", 0); 346 setOption(OPT_CATEGORY_PLAYER, "soundtouch", 1); 347 setOption(OPT_CATEGORY_PLAYER, "subtitle", 1); 348 setOption(OPT_CATEGORY_PLAYER, "max-buffer-size", 15 * 1024 * 1024); 349 setOption(OPT_CATEGORY_PLAYER, "max-buffer-duration", 10 * 1000); 350 setOption(OPT_CATEGORY_PLAYER, "enable-accurate-seek", 1); 351 setLogEnabled(false); 352 } 353 354 private void setupPlayer() { 355 // setOption(OPT_CATEGORY_FORMAT, "auto_convert", 0); // only for concat protocol 356 setOption(OPT_CATEGORY_FORMAT, "reconnect", 1); 357 setOption(OPT_CATEGORY_FORMAT, "timeout", mNetworkTimeoutInUs); 358 native_setup(); 359 } 360 361 /* 362 * Update the BDCloudMediaPlayer SurfaceTexture. Call after setting a new display surface. 363 */ 364 private native void _setVideoSurface(Surface surface); 365 366 /** 367 * 设置 {@link SurfaceHolder} 用于显示视频 368 * <p> 369 * 如果想显示视频,需要设置SurfaceHolder或者Surface。既不设置该方法也不设置{@link #setSurface(Surface)}方法,会导致仅有音频播放。将 370 * SurfaceHolder或者Surface设置为空,也会导致仅播放音频。 371 * 372 * @param sh the SurfaceHolder to use for video display 373 */ 374 @Override 375 public void setDisplay(SurfaceHolder sh) { 376 mSurfaceHolder = sh; 377 Surface surface; 378 if (sh != null) { 379 surface = sh.getSurface(); 380 } else { 381 surface = null; 382 } 383 _setVideoSurface(surface); 384 updateSurfaceScreenOn(); 385 } 386 387 /** 388 * 设置{@link Surface}来显示视频,与接口{@link #setDisplay(SurfaceHolder)}功能类似,但不支持{@link #setScreenOnWhilePlaying(boolean)} 389 * 接口的设置。当调用该接口时,之前设置的Surface或SurfaceHolder将被替换。设置为null会仅播放音频。 390 * <p> 391 * 如果设置的Surface会往{@link SurfaceTexture}发送frames,{@link SurfaceTexture#getTimestamp()}接口返回的时间戳会有未指定的零点。这些时间戳 392 * 在不同媒体资源、同一资源的不同实例、同一程序的多次运行的情况中不具有可比性。这个时间戳会单调递增,并且不受时间调整的影响,但在位置设置后会被重置。 393 * 394 * @param surface The {@link Surface} to be used for the video portion of the media. 395 */ 396 @Override 397 public void setSurface(Surface surface) { 398 if (mScreenOnWhilePlaying && surface != null) { 399 DebugLog.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface"); 400 } 401 mSurfaceHolder = null; 402 _setVideoSurface(surface); 403 updateSurfaceScreenOn(); 404 } 405 406 /** 407 * 设置播放源 408 * 409 * @param context the Context to use when resolving the Uri 410 * @param uri the Content URI of the data you want to play 411 * @throws IllegalStateException if it is called in an invalid state 412 */ 413 @Override 414 public void setDataSource(Context context, Uri uri) 415 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { 416 setDataSource(context, uri, null); 417 } 418 419 /** 420 * 设置播放源,可设置请求头信息 421 * 422 * @param context the Context to use when resolving the Uri 423 * @param uri the Content URI of the data you want to play 424 * @param headers the headers to be sent together with the request for the data Note that the cross domain 425 * redirection is allowed by default, but that can be changed with key/value pairs through the headers 426 * parameter with "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to 427 * disallow or allow cross domain redirection. 428 * @throws IllegalStateException if it is called in an invalid state 429 */ 430 @Override 431 public void setDataSource(Context context, Uri uri, Map<String, String> headers) 432 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { 433 final String scheme = uri.getScheme(); 434 if (ContentResolver.SCHEME_FILE.equals(scheme)) { 435 setDataSource(uri.getPath()); 436 return; 437 } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) && Settings.AUTHORITY.equals(uri.getAuthority())) { 438 // Redirect ringtones to go directly to underlying provider 439 uri = RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.getDefaultType(uri)); 440 if (uri == null) { 441 throw new FileNotFoundException("Failed to resolve default ringtone"); 442 } 443 } 444 445 AssetFileDescriptor fd = null; 446 try { 447 ContentResolver resolver = context.getContentResolver(); 448 fd = resolver.openAssetFileDescriptor(uri, "r"); 449 if (fd == null) { 450 return; 451 } 452 // Note: using getDeclaredLength so that our behavior is the same 453 // as previous versions when the content provider is returning 454 // a full file. 455 if (fd.getDeclaredLength() < 0) { 456 setDataSource(fd.getFileDescriptor()); 457 } else { 458 setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getDeclaredLength()); 459 } 460 return; 461 } catch (SecurityException ignored) { 462 } catch (IOException ignored) { 463 } finally { 464 if (fd != null) { 465 fd.close(); 466 } 467 } 468 469 setDataSource(uri.toString(), headers); 470 } 471 472 /** 473 * 设置播放源 (file-path or http/rtsp URL) 474 * 475 * @param path the path of the file, or the http/rtsp URL of the stream you want to play 476 * @throws IllegalStateException if it is called in an invalid state 477 * <p> 478 * <p> 479 * When <code>path</code> refers to a local file, the file may actually be opened by a process other 480 * than the calling application. This implies that the pathname should be an absolute path (as any other 481 * process runs with unspecified current working directory), and that the pathname should reference a 482 * world-readable file. 483 */ 484 @Override 485 public void setDataSource(String path) 486 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { 487 mDataSource = path; 488 this.onInitingStat(); 489 setupPlayer(); 490 _setDataSource(path, null, null); 491 492 if (isLocalFilePath(path)) { 493// Log.d(TAG, "isLocalFile=" + path); 494 // try to fetch 495 String newFile = path.replace("file://", ""); 496 SharedPreferences sp = appContext.getSharedPreferences(SP_FILE_FOR_KEY, 0); 497 String savedKey = sp.getString(newFile, null); 498 if (savedKey != null) { 499 try { 500 String key = LocalHlsSec.decryptStr(appContext, savedKey); 501 502 if (key != null && key.length() > 0) { 503 this.setLocalDecryptKeyForHLS(key); 504 } 505 } catch (Exception e) { 506 Log.d(TAG, "", e); 507 } 508 509 } 510 } else { 511 setOption(OPT_CATEGORY_PLAYER, "enable_p2p", sEnableP2P ? 1 : 0); 512 setOption(OPT_CATEGORY_PLAYER, "enable_cache", sEnableCache ? 1 : 0); 513 setOption(OPT_CATEGORY_FORMAT, "cache_dir", sEnableCache ? sCacheRootPath : ""); 514 } 515 } 516 517 /** 518 * 设置播放源 (file-path or http/rtsp URL),可设置网络请求头部信息。 519 * 520 * @param path the path of the file, or the http/rtsp URL of the stream you want to play 521 * @param headers the headers associated with the http request for the stream you want to play 522 * @throws IllegalStateException if it is called in an invalid state 523 */ 524 public void setDataSource(String path, Map<String, String> headers) 525 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { 526 if (headers != null && !headers.isEmpty()) { 527 StringBuilder sb = new StringBuilder(); 528 for (Map.Entry<String, String> entry : headers.entrySet()) { 529 sb.append(entry.getKey()); 530 sb.append(": "); 531 String value = entry.getValue(); 532 if (!TextUtils.isEmpty(value)) { 533 sb.append(entry.getValue()); 534 } 535 sb.append("\r\n"); 536 } 537 mCustomisedHeaders = sb.toString(); 538 setOption(OPT_CATEGORY_FORMAT, "headers", mCustomisedHeaders); 539 } else { 540 mCustomisedHeaders = null; 541 } 542 543 setDataSource(path); 544 } 545 546 /** 547 * 设置播放源 (FileDescriptor). It is the caller's responsibility to close the file descriptor. It 548 * is safe to do so as soon as this call returns. 549 * 550 * @param fd the FileDescriptor for the file you want to play 551 * @throws IllegalStateException if it is called in an invalid state 552 */ 553 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) 554 @Override 555 public void setDataSource(FileDescriptor fd) throws IOException, IllegalArgumentException, IllegalStateException { 556 setupPlayer(); 557 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) { 558 int native_fd = -1; 559 try { 560 Field f = fd.getClass().getDeclaredField("descriptor"); // NoSuchFieldException 561 f.setAccessible(true); 562 native_fd = f.getInt(fd); // IllegalAccessException 563 } catch (NoSuchFieldException e) { 564 throw new RuntimeException(e); 565 } catch (IllegalAccessException e) { 566 throw new RuntimeException(e); 567 } 568 _setDataSourceFd(native_fd); 569 } else { 570 ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd); 571 try { 572 _setDataSourceFd(pfd.getFd()); 573 } finally { 574 pfd.close(); 575 } 576 } 577 } 578 579 /** 580 * 设置播放源 (FileDescriptor). The FileDescriptor must be seekable (N.B. a LocalSocket is not 581 * seekable). It is the caller's responsibility to close the file descriptor. It is safe to do so as soon as this 582 * call returns. 583 * 584 * @param fd the FileDescriptor for the file you want to play 585 * @param offset the offset into the file where the data to be played starts, in bytes 586 * @param length the length in bytes of the data to be played 587 * @throws IllegalStateException if it is called in an invalid state 588 */ 589 private void setDataSource(FileDescriptor fd, long offset, long length) 590 throws IOException, IllegalArgumentException, IllegalStateException { 591 // FIXME: handle offset, length 592 setDataSource(fd); 593 } 594 595 public void setDataSource(IMediaDataSource mediaDataSource) 596 throws IllegalArgumentException, SecurityException, IllegalStateException { 597 setupPlayer(); 598 _setDataSource(mediaDataSource); 599 } 600 601 private native void _setDataSource(String path, String[] keys, String[] values) 602 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; 603 604 private native void _setDataSourceFd(int fd) 605 throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; 606 607 private native void _setDataSource(IMediaDataSource mediaDataSource) 608 throws IllegalArgumentException, SecurityException, IllegalStateException; 609 610 /** 611 * 获取播放路径 612 * 613 * @return 614 */ 615 @Override 616 public String getDataSource() { 617 return mDataSource; 618 } 619 620 /** 621 * 异步准备,播放器仅支持异步准备。您可以在准备好后调用start来启动播放 622 * 623 * @throws IllegalStateException 624 */ 625 @Override 626 public void prepareAsync() throws IllegalStateException { 627 _prepareAsync(); 628 } 629 630 private native void _prepareAsync() throws IllegalStateException; 631 632 /** 633 * 启动播放 634 * 要求播放源已经准备好 635 * 636 * @throws IllegalStateException 637 */ 638 @Override 639 public void start() throws IllegalStateException { 640 stayAwake(true); 641 this.onResumeStat(); 642 _start(); 643 } 644 645 private native void _start() throws IllegalStateException; 646 647 /** 648 * 停止播放 649 * 650 * @throws IllegalStateException 651 */ 652 @Override 653 public void stop() throws IllegalStateException { 654 stayAwake(false); 655 this.onStopStat(0, 0); 656 this.onEndStat(); 657 _stop(); 658 } 659 660 private native void _stop() throws IllegalStateException; 661 662 /** 663 * 暂停播放 664 * 665 * @throws IllegalStateException 666 */ 667 @Override 668 public void pause() throws IllegalStateException { 669 stayAwake(false); 670 this.onPauseStat(); 671 _pause(); 672 } 673 674 private native void _pause() throws IllegalStateException; 675 676 /** 677 * 设置保持唤醒模式 678 * 679 * @param context 680 * @param mode 该模式为PowerManager.newWakeLock的接口参数 681 */ 682 @SuppressLint("Wakelock") 683 @Override 684 public void setWakeMode(Context context, int mode) { 685 boolean washeld = false; 686 if (mWakeLock != null) { 687 if (mWakeLock.isHeld()) { 688 washeld = true; 689 mWakeLock.release(); 690 } 691 mWakeLock = null; 692 } 693 694 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 695 mWakeLock = pm.newWakeLock(mode | PowerManager.ON_AFTER_RELEASE, BDCloudMediaPlayer.class.getName()); 696 mWakeLock.setReferenceCounted(false); 697 if (washeld) { 698 mWakeLock.acquire(); 699 } 700 } 701 702 /** 703 * 设置播放时屏幕保持,仅在设置过SurfaceHolder时有效。 704 * 705 * @param screenOn 706 */ 707 @Override 708 public void setScreenOnWhilePlaying(boolean screenOn) { 709 if (mScreenOnWhilePlaying != screenOn) { 710 if (screenOn && mSurfaceHolder == null) { 711 DebugLog.w(TAG, "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder"); 712 } 713 mScreenOnWhilePlaying = screenOn; 714 updateSurfaceScreenOn(); 715 } 716 } 717 718 @SuppressLint("Wakelock") 719 private void stayAwake(boolean awake) { 720 if (mWakeLock != null) { 721 if (awake && !mWakeLock.isHeld()) { 722 mWakeLock.acquire(); 723 } else if (!awake && mWakeLock.isHeld()) { 724 mWakeLock.release(); 725 } 726 } 727 mStayAwake = awake; 728 updateSurfaceScreenOn(); 729 } 730 731 private void updateSurfaceScreenOn() { 732 if (mSurfaceHolder != null) { 733 mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake); 734 } 735 } 736 737 /** 738 * 获取音视频track的信息 739 * 740 * @return 741 */ 742 @Override 743 public BDCloudTrackInfo[] getTrackInfo() { 744 Bundle bundle = getMediaMeta(); 745 if (bundle == null) { 746 return null; 747 } 748 749 BDCloudMediaMeta mediaMeta = BDCloudMediaMeta.parse(bundle); 750 if (mediaMeta == null || mediaMeta.mStreams == null) { 751 return null; 752 } 753 754 ArrayList<BDCloudTrackInfo> trackInfos = new ArrayList<BDCloudTrackInfo>(); 755 for (BDCloudMediaMeta.BDCloudStreamMeta streamMeta : mediaMeta.mStreams) { 756 BDCloudTrackInfo trackInfo = new BDCloudTrackInfo(streamMeta); 757 if (streamMeta.mType.equalsIgnoreCase(BDCloudMediaMeta.IJKM_VAL_TYPE__VIDEO)) { 758 trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_VIDEO); 759 } else if (streamMeta.mType.equalsIgnoreCase(BDCloudMediaMeta.IJKM_VAL_TYPE__AUDIO)) { 760 trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_AUDIO); 761 } else if (streamMeta.mType.equalsIgnoreCase(BDCloudMediaMeta.IJKM_VAL_TYPE__TIMEDTEXT)) { 762 trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT); 763 } 764 trackInfos.add(trackInfo); 765 } 766 767 return trackInfos.toArray(new BDCloudTrackInfo[trackInfos.size()]); 768 } 769 770 // TODO: @Override 771 public int getSelectedTrack(int trackType) { 772 switch (trackType) { 773 case ITrackInfo.MEDIA_TRACK_TYPE_VIDEO: 774 return (int) _getPropertyLong(FFP_PROP_INT64_SELECTED_VIDEO_STREAM, -1); 775 case ITrackInfo.MEDIA_TRACK_TYPE_AUDIO: 776 return (int) _getPropertyLong(FFP_PROP_INT64_SELECTED_AUDIO_STREAM, -1); 777 case ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT: 778 return (int) _getPropertyLong(FFP_PROP_INT64_SELECTED_TIMEDTEXT_STREAM, -1); 779 default: 780 return -1; 781 } 782 } 783 784 // experimental, should set DEFAULT_MIN_FRAMES and MAX_MIN_FRAMES to 25 785 // TODO: @Override 786 public void selectTrack(int track) { 787 _setStreamSelected(track, true); 788 } 789 790 // experimental, should set DEFAULT_MIN_FRAMES and MAX_MIN_FRAMES to 25 791 // TODO: @Override 792 public void deselectTrack(int track) { 793 _setStreamSelected(track, false); 794 } 795 796 private native void _setStreamSelected(int stream, boolean select); 797 798 /** 799 * 获取视频宽度 800 * 801 * @return 802 */ 803 @Override 804 public int getVideoWidth() { 805 return mVideoWidth; 806 } 807 808 /** 809 * 获取视频高度 810 * 811 * @return 812 */ 813 @Override 814 public int getVideoHeight() { 815 return mVideoHeight; 816 } 817 818 /** 819 * 获取采样纵横比的分子 820 * 821 * @return 822 */ 823 @Override 824 public int getVideoSarNum() { 825 return mVideoSarNum; 826 } 827 828 /** 829 * 获取采样纵横比的分母 830 * 831 * @return 832 */ 833 @Override 834 public int getVideoSarDen() { 835 return mVideoSarDen; 836 } 837 838 /** 839 * 是否正在播放 840 * 841 * @return 842 */ 843 @Override 844 public native boolean isPlaying(); 845 846 /** 847 * 快速切换到某个时间点进行播放 848 * 849 * @param msec 850 * @throws IllegalStateException 851 */ 852 @Override 853 public void seekTo(long msec) throws IllegalStateException { 854 this.onSeekToStat(this.getCurrentPosition() / 1000, msec / 1000); 855 _seekTo(msec); 856 } 857 858 private native void _seekTo(long msec) throws IllegalStateException; 859 860 /** 861 * 获取当前播放位置,单位为毫秒 862 * 863 * @return 864 */ 865 @Override 866 public native long getCurrentPosition(); 867 868 /** 869 * 获取音视频时长,单位为毫秒 870 * 871 * @return 872 */ 873 @Override 874 public native long getDuration(); 875 876 /** 877 * 释放资源 878 * Releases resources associated with this BDCloudMediaPlayer object. It is considered good practice to call this 879 * method when you're done using the BDCloudMediaPlayer. In particular, whenever an Activity of an application is 880 * paused (its onPause() method is called), or stopped (its onStop() method is called), this method should be 881 * invoked to release the BDCloudMediaPlayer object, unless the application has a special need to keep the object 882 * around. In addition to unnecessary resources (such as memory and instances of codecs) being held, failure to call 883 * this method immediately if a BDCloudMediaPlayer object is no longer needed may also lead to continuous battery 884 * consumption for mobile devices, and playback failure for other applications if no multiple instances of the same 885 * codec are supported on a device. Even if multiple instances of the same codec are supported, some performance 886 * degradation may be expected when unnecessary multiple instances are used at the same time. 887 */ 888 @Override 889 public void release() { 890 stayAwake(false); 891 this.onEndStat(); 892 updateSurfaceScreenOn(); 893 resetListeners(); 894 APMEventHandle.getInstance(appContext).release(); 895 _release(); 896 } 897 898 private native void _release(); 899 900 /** 901 * 重置,将状态重置为IDLE 902 * 重置后需重新设置播放源 903 */ 904 @Override 905 public void reset() { 906 stayAwake(false); 907 this.onEndStat(); 908 _stop(); 909 _reset(); 910 // make sure none of the listeners get called anymore 911 mEventHandler.removeCallbacksAndMessages(null); 912 913 mVideoWidth = 0; 914 mVideoHeight = 0; 915 } 916 917 private native void _reset(); 918 919 /** 920 * 设置是否循环播放 921 * 922 * @param looping whether to loop or not 923 */ 924 @Override 925 public void setLooping(boolean looping) { 926 int loopCount = looping ? 0 : 1; 927 setOption(OPT_CATEGORY_PLAYER, "loop", loopCount); 928 _setLoopCount(loopCount); 929 } 930 931 private native void _setLoopCount(int loopCount); 932 933 /** 934 * 是否循环播放 935 * 936 * @return true if the MediaPlayer is currently looping, false otherwise 937 */ 938 @Override 939 public boolean isLooping() { 940 int loopCount = _getLoopCount(); 941 return loopCount != 1; 942 } 943 944 private native int _getLoopCount(); 945 946 /** 947 * 设置播放速度,目前仅支持Android 6.0及以上版本 948 * 949 * @param speed 950 */ 951 @TargetApi(Build.VERSION_CODES.M) 952 public void setSpeed(float speed) { 953 if (speed == 0) { 954 return; 955 } 956 _setPropertyFloat(FFP_PROP_FLOAT_PLAYBACK_RATE, speed); 957 } 958 959 /** 960 * 获取播放速度,目前仅支持Android 6.0及以上版本 961 * 962 * @param speed 963 * @return 964 */ 965 @TargetApi(Build.VERSION_CODES.M) 966 public float getSpeed(float speed) { 967 return _getPropertyFloat(FFP_PROP_FLOAT_PLAYBACK_RATE, .0f); 968 } 969 970 /** 971 * 获取当前视频的decoder类型,1为软解;2为硬解 972 * 973 * @return 974 */ 975 public int getVideoDecoder() { 976 return (int) _getPropertyLong(FFP_PROP_INT64_VIDEO_DECODER, FFP_PROPV_DECODER_UNKNOWN); 977 } 978 979 /** 980 * 获取视频输出帧率 981 * 982 * @return 983 */ 984 public float getVideoOutputFramesPerSecond() { 985 return _getPropertyFloat(PROP_FLOAT_VIDEO_OUTPUT_FRAMES_PER_SECOND, 0.0f); 986 } 987 988 /** 989 * 获取视频解码帧率 990 * 991 * @return 992 */ 993 public float getVideoDecodeFramesPerSecond() { 994 return _getPropertyFloat(PROP_FLOAT_VIDEO_DECODE_FRAMES_PER_SECOND, 0.0f); 995 } 996 997 /** 998 * 获取视频已缓冲好的长度 999 * 1000 * @return 1001 */ 1002 public long getVideoCachedDuration() { 1003 return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_DURATION, 0); 1004 } 1005 1006 /** 1007 * 获取音频已缓冲好的长度 1008 * 1009 * @return 1010 */ 1011 public long getAudioCachedDuration() { 1012 return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_DURATION, 0); 1013 } 1014 1015 /** 1016 * 获取视频已缓冲好的字节数 1017 * 1018 * @return 1019 */ 1020 public long getVideoCachedBytes() { 1021 return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_BYTES, 0); 1022 } 1023 1024 /** 1025 * 获取音频已缓冲好的字节数 1026 * 1027 * @return 1028 */ 1029 public long getAudioCachedBytes() { 1030 return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_BYTES, 0); 1031 } 1032 1033 /** 1034 * 获取视频已缓冲好的包数 1035 * 1036 * @return 1037 */ 1038 public long getVideoCachedPackets() { 1039 return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_PACKETS, 0); 1040 } 1041 1042 /** 1043 * 获取音频已缓冲好的包数 1044 * 1045 * @return 1046 */ 1047 public long getAudioCachedPackets() { 1048 return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_PACKETS, 0); 1049 } 1050 1051 public long getAsyncStatisticBufBackwards() { 1052 return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_BACKWARDS, 0); 1053 } 1054 1055 public long getAsyncStatisticBufForwards() { 1056 return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_FORWARDS, 0); 1057 } 1058 1059 public long getAsyncStatisticBufCapacity() { 1060 return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_CAPACITY, 0); 1061 } 1062 1063 public long getTrafficStatisticByteCount() { 1064 return _getPropertyLong(FFP_PROP_INT64_TRAFFIC_STATISTIC_BYTE_COUNT, 0); 1065 } 1066 1067 public long getCacheStatisticPhysicalPos() { 1068 return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_PHYSICAL_POS, 0); 1069 } 1070 1071 public long getCacheStatisticBufForwards() { 1072 return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_BUF_FORWARDS, 0); 1073 } 1074 1075 public long getCacheStatisticFilePos() { 1076 return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_FILE_POS, 0); 1077 } 1078 1079 public long getCacheStatisticCountBytes() { 1080 return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_COUNT_BYTES, 0); 1081 } 1082 1083 public long getBitRate() { 1084 return _getPropertyLong(FFP_PROP_INT64_BIT_RATE, 0); 1085 } 1086 1087 /** 1088 * 获取网络下载速度 1089 * 1090 * @return 1091 */ 1092 public long getDownloadSpeed() { 1093 return _getPropertyLong(FFP_PROP_INT64_TCP_SPEED, 0); 1094 } 1095 1096 public long getSeekLoadDuration() { 1097 return _getPropertyLong(FFP_PROP_INT64_LATEST_SEEK_LOAD_DURATION, 0); 1098 } 1099 1100 private native float _getPropertyFloat(int property, float defaultValue); 1101 1102 private native void _setPropertyFloat(int property, float value); 1103 1104 private native long _getPropertyLong(int property, long defaultValue); 1105 1106 private native void _setPropertyLong(int property, long value); 1107 1108 /** 1109 * 设置左右声道的音量 1110 * 1111 * @param leftVolume 1112 * @param rightVolume 1113 */ 1114 @Override 1115 public native void setVolume(float leftVolume, float rightVolume); 1116 1117 @Override 1118 public native int getAudioSessionId(); 1119 1120 1121 /** 1122 * 获取sdk版本,形式为 xx.xx.xx 1123 * 1124 * @return 1125 */ 1126 public static String getSdkVersion() { 1127 return SDK_VERSION; 1128 } 1129 1130 /** 1131 * 获取媒体信息 1132 * 包含解码信息与音视频流信息 1133 * 1134 * @return 1135 */ 1136 @Override 1137 public MediaInfo getMediaInfo() { 1138 MediaInfo mediaInfo = new MediaInfo(); 1139 mediaInfo.mMediaPlayerName = "bdcloudplayer"; 1140 1141 String videoCodecInfo = _getVideoCodecInfo(); 1142 if (!TextUtils.isEmpty(videoCodecInfo)) { 1143 String nodes[] = videoCodecInfo.split(","); 1144 if (nodes.length >= 2) { 1145 mediaInfo.mVideoDecoder = nodes[0]; 1146 mediaInfo.mVideoDecoderImpl = nodes[1]; 1147 } else if (nodes.length >= 1) { 1148 mediaInfo.mVideoDecoder = nodes[0]; 1149 mediaInfo.mVideoDecoderImpl = ""; 1150 } 1151 } 1152 1153 String audioCodecInfo = _getAudioCodecInfo(); 1154 if (!TextUtils.isEmpty(audioCodecInfo)) { 1155 String nodes[] = audioCodecInfo.split(","); 1156 if (nodes.length >= 2) { 1157 mediaInfo.mAudioDecoder = nodes[0]; 1158 mediaInfo.mAudioDecoderImpl = nodes[1]; 1159 } else if (nodes.length >= 1) { 1160 mediaInfo.mAudioDecoder = nodes[0]; 1161 mediaInfo.mAudioDecoderImpl = ""; 1162 } 1163 } 1164 1165 try { 1166 mediaInfo.mMeta = BDCloudMediaMeta.parse(_getMediaMeta()); 1167 } catch (Throwable e) { 1168 e.printStackTrace(); 1169 } 1170 return mediaInfo; 1171 } 1172 1173 1174 public void setMediaInputType(int mode) { 1175 switch (mode) { 1176 case SOURCE_SWITCH_SMOOTH_MODE: 1177 setOption(OPT_CATEGORY_PLAYER,"iformat","adapt_media"); 1178 setOption(OPT_CATEGORY_FORMAT,"get_master_plist",0); 1179 break; 1180 case SOURCE_SWITCH_SMOOTH_HLS_MODE: 1181 setOption(OPT_CATEGORY_PLAYER,"iformat","adapt_media"); 1182 setOption(OPT_CATEGORY_FORMAT,"get_master_plist",1); 1183 break; 1184 case SOURCE_SWITCH_NORMAL_MODE: 1185 setOption(OPT_CATEGORY_PLAYER,"iformat",null); 1186 break; 1187 default: 1188 setOption(OPT_CATEGORY_PLAYER,"iformat",null); 1189 break; 1190 } 1191 } 1192 1193 /** 1194 * 切换多分辨率 1195 * 需要首先调用getVariantInfo拿到数组 1196 * 1197 * @param index VariantInfo数组的下标。 1198 * @return 1199 */ 1200 public boolean selectResolutionByIndex(int index) { 1201 // the same time 1202 if (getCurrentVariantIndex() == index) { 1203 Log.d(TAG, "currentVariantIndex is equals to index setted~" + index); 1204 return false; 1205 } else if (index < 0 || index >= this.getVariantInfo().length) { 1206 Log.d(TAG, "index is not in [0," + this.getVariantInfo().length + ")"); 1207 return false; 1208 } 1209 long currentPosition = this.getCurrentPosition(); 1210 this.stop(); 1211 native_setup(); 1212 // option of category[OPT_CATEGORY_FORMAT] should be restored before preparing 1213 // setOption(OPT_CATEGORY_FORMAT, "auto_convert", 0); // for concat protocol only 1214 setOption(OPT_CATEGORY_FORMAT, "reconnect", 1); 1215 setOption(OPT_CATEGORY_FORMAT, "timeout", mNetworkTimeoutInUs); 1216 setOption(OPT_CATEGORY_FORMAT, "cache_dir", sEnableCache ? sCacheRootPath : ""); 1217 if (!TextUtils.isEmpty(mCustomisedHeaders)) { 1218 setOption(OPT_CATEGORY_FORMAT, "headers", mCustomisedHeaders); 1219 } 1220 this.selectVariantByIndex(index); 1221 setInitPlayPosition(currentPosition); 1222 this.prepareAsync(); 1223 return true; 1224 } 1225 1226 public boolean selectMediaByIndex(int index) { 1227 if (getCurrentMediaIndex() == index) { 1228 Log.d(TAG, "Is the same index :" + index); 1229 return false; 1230 } else if (index < 0 || index >= this.getMediaItems().length) { 1231 Log.d(TAG, "index is not in [0," + this.getMediaItems().length + ")"); 1232 return false; 1233 } 1234 return setMediaItemIndex(index); 1235 } 1236 1237 /** 1238 * 设置初始播放位置, 需在prepareAsync之前调用 1239 * 1240 * @param positionInMilliSeconds 1241 */ 1242 public void setInitPlayPosition(long positionInMilliSeconds) { 1243 this.setOption(OPT_CATEGORY_PLAYER, "seek-at-start", positionInMilliSeconds); 1244 } 1245 1246 /** 1247 * 默认:优先硬解,无法硬解时软解 1248 */ 1249 public static final int DECODE_AUTO = 0; 1250 /** 1251 * 设置为软解 1252 */ 1253 public static final int DECODE_SW = 1; 1254 1255 private int decodeMode = DECODE_AUTO; 1256 1257 /** 1258 * 设置软硬解模式,默认为auto模式(自动检测,优先硬解) 1259 * 1260 * @param mode 取值为DECODE_AUTO或DECODE_SW 1261 */ 1262 public void setDecodeMode(int mode) { 1263 if (mode >= 0 && mode <= 1) { 1264 decodeMode = mode; 1265 if (decodeMode == DECODE_AUTO) { 1266 this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1); 1267 this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-all-videos", 1); 1268 this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1); 1269 this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, 1270 "mediacodec-handle-resolution-change", 1); 1271 } else { 1272 this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0); 1273 this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-all-videos", 0); 1274 this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 0); 1275 this.setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, 1276 "mediacodec-handle-resolution-change", 0); 1277 } 1278 } else { 1279 DebugLog.e(TAG, "decodeMode shoule be DECODE_AUTO or DECODE_SW"); 1280 } 1281 } 1282 1283 /** 1284 * 获取之前设置的解码模式 1285 * 1286 * @return 1287 */ 1288 public int getDecodeMode() { 1289 return decodeMode; 1290 } 1291 1292// /** 1293// * 设置是否为直播地址,播放器会自动优化配置。 1294// * @param isLiveUrl true表示为直播链接;false表示非直播链接 1295// */ 1296// public void setIsLiveUrl(boolean isLiveUrl) { 1297// if (isLiveUrl) { 1298// // infbuf在底层的逻辑有变 1299// setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1); 1300// setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0); 1301// } else { 1302// setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 0); 1303// setOption(BDCloudMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 1); 1304// } 1305// 1306// } 1307 1308 private native String getCdnIp(); 1309 1310 /** 1311 * 获得当前多码率视频的index 1312 * 1313 * @return 1314 */ 1315 public native int getCurrentVariantIndex(); 1316 1317 /** 1318 * 获得多码率无缝切换模式下当前的媒体index 1319 * 1320 * @return 1321 */ 1322 public native int getCurrentMediaIndex(); 1323 1324 public native boolean setMediaItemIndex(int index); 1325 1326 /** 1327 * 设置多码率index。特注:播放过程中设置多码率请使用selectResolutionByIndex,该函数会帮您处理播放器状态。 1328 * 1329 * @param index 1330 */ 1331 public native void selectVariantByIndex(int index); 1332 1333 /** 1334 * DRM加密相关,一般不需设置 1335 * 1336 * @param id 1337 */ 1338 public native void setCustomizedPlayerIdForHLS(String id); 1339 1340 /** 1341 * DRM加密相关,一般不需设置 1342 * 1343 * @param key 1344 */ 1345 public native void setCustomizedPlayerKeyForHLS(String key); 1346 1347 /** 1348 * DRM加密-token加密,通过该接口设置token 1349 * 1350 * @param token 1351 */ 1352 public native void setDecryptTokenForHLS(String token); 1353 1354 protected native void setLocalDecryptKeyForHLS(String key); 1355 1356 /** 1357 * 设置 最大缓冲区持续时间 1358 * 1359 * @param duration 单位 ms 1360 */ 1361 public void setMaxCacheDuration(int duration){ 1362 setOption(OPT_CATEGORY_PLAYER, "max-buffer-duration", duration); 1363 } 1364 1365 /** 1366 * 设置 最大缓冲区长度 1367 * 1368 * @param size 1369 */ 1370 public void setMaxCacheSizeInBytes(int size) { 1371 maxCacheSizeForApm = size; 1372 this.setOption(OPT_CATEGORY_PLAYER, "max-buffer-size", size); 1373 } 1374 1375 /** 1376 * 设置 缓冲过程中,起播数据时长 1377 * 1378 * @param time 1379 */ 1380 public void setBufferTimeInMs(int time) { 1381 cachePauseTimeForApm = time; 1382 this.setOption(OPT_CATEGORY_PLAYER, "buffer-time-in-ms", time); 1383 } 1384 1385 /** 1386 * 设置 缓冲过程中,起播数据字节长度 1387 * 1388 * @param size 1389 */ 1390 public void setBufferSizeInBytes(int size) { 1391 this.setOption(OPT_CATEGORY_PLAYER, "buffer-size-in-bytes", size); 1392 } 1393 1394 /** 1395 * 设置probe(音视频格式探测)最大时长,单位是毫秒 1396 * 1397 * @param maxProbeTime 1398 */ 1399 public void setMaxProbeTime(int maxProbeTime) { 1400 firstBufferingTimeForApm = maxProbeTime; 1401 this.setOption(OPT_CATEGORY_PLAYER, "max-probe-time", maxProbeTime); 1402 } 1403 1404 /** 1405 * 设置probe(音视频格式探测)最大数据大小 1406 * 1407 * @param maxProbeSize 1408 */ 1409 public void setMaxProbeSize(int maxProbeSize) { 1410 this.setOption(OPT_CATEGORY_PLAYER, "max-probe-size", maxProbeSize); 1411 } 1412 1413 /** 1414 * Setting timeout threshold of network connecting and reading process 1415 * 1416 * @param timeout timeout in us 1417 */ 1418 public void setTimeoutInUs(int timeout) { 1419 mNetworkTimeoutInUs = timeout > 0 ? timeout : mNetworkTimeoutInUs; 1420 this.setOption(OPT_CATEGORY_FORMAT, "timeout", mNetworkTimeoutInUs); 1421 } 1422 1423 /** 1424 * 设置是否开启追帧播放功能 1425 * 1426 * @param isEnable 是否开启 1427 */ 1428 public void toggleFrameChasing(boolean isEnable) { 1429 toggleFrameChasingForApm = isEnable; 1430 this.setOption(OPT_CATEGORY_PLAYER, "framechasing", isEnable ? 1 : 0); 1431 } 1432 1433 public native void setMediaItems(String[] mediaItems); 1434 1435 /** 1436 * 获得多码率视频的各码率信息 1437 * 1438 * @return 1439 */ 1440 public native String[] getVariantInfo(); 1441 1442 public native String[] getMediaItems(); 1443 1444 /** 1445 * 是否显示debug消息 1446 * 1447 * @param enable 当为false时,将仅显示warn级别及以上的消息; 1448 */ 1449 @Override 1450 public void setLogEnabled(boolean enable) { 1451 1452 if (enable) { 1453 native_setLogLevel(IJK_LOG_DEFAULT); 1454 DebugLog.setDebugLogEnabled(true); 1455 } else { 1456 native_setLogLevel(IJK_LOG_WARN); 1457 DebugLog.setDebugLogEnabled(false); 1458 } 1459 } 1460 1461 /** 1462 * 是否可播放,当前始终为true 1463 * 1464 * @return 1465 */ 1466 @Override 1467 public boolean isPlayable() { 1468 return true; 1469 } 1470 1471 private native String _getVideoCodecInfo(); 1472 1473 private native String _getAudioCodecInfo(); 1474 1475 /** 1476 * 设置额外信息,一般不需设置 1477 * 1478 * @param category 1479 * @param name 1480 * @param value 1481 */ 1482 public void setOption(int category, String name, String value) { 1483 _setOption(category, name, value); 1484 } 1485 1486 /** 1487 * 设置额外信息,一般不需设置 1488 * 1489 * @param category 1490 * @param name 1491 * @param value 1492 */ 1493 public void setOption(int category, String name, long value) { 1494 _setOption(category, name, value); 1495 } 1496 1497 private native void _setOption(int category, String name, String value); 1498 1499 private native void _setOption(int category, String name, long value); 1500 1501 public Bundle getMediaMeta() { 1502 return _getMediaMeta(); 1503 } 1504 1505 private native Bundle _getMediaMeta(); 1506 1507 public static String getColorFormatName(int mediaCodecColorFormat) { 1508 return _getColorFormatName(mediaCodecColorFormat); 1509 } 1510 1511 private static native String _getColorFormatName(int mediaCodecColorFormat); 1512 1513 /** 1514 * 当前该设置不生效 1515 * 1516 * @param streamtype 1517 */ 1518 @Override 1519 public void setAudioStreamType(int streamtype) { 1520 // do nothing 1521 } 1522 1523 /** 1524 * 当前该设置不生效 1525 * 1526 * @param keepInBackground 1527 */ 1528 @Override 1529 public void setKeepInBackground(boolean keepInBackground) { 1530 // do nothing 1531 } 1532 1533 private native void native_init(Object BDCloudMediaPlayer_this); 1534 1535 private native void native_setup(); 1536 1537 private native void native_finalize(); 1538 1539 private native void native_message_loop(Object BDCloudMediaPlayer_this); 1540 1541 protected void finalize() throws Throwable { 1542 super.finalize(); 1543 native_finalize(); 1544 } 1545 1546 protected void onErrorStat(int what, int extra) { 1547 try { 1548 APMEventHandle.getInstance(appContext).onPlayEnd(what, extra, 1); 1549 } catch (Exception e) { 1550 Log.d(TAG, "APMEventHandle exception:" + e.getMessage()); 1551 } 1552 } 1553 1554 protected void onCompleteStat(int what, int extra) { 1555 APMEventHandle.getInstance(appContext).onPlayEnd(what, extra, 2); 1556 } 1557 1558 protected void onStopStat(int what, int extra) { 1559 APMEventHandle.getInstance(appContext).onPlayEnd(what, extra, 3); 1560 } 1561 1562 /** 1563 * when play completeOronErrorOrStopOrRelease mediaplayer, 但不能多发 1564 */ 1565 protected void onEndStat() { 1566 // deal with complete/stop/reset/release multi Come In 1567 if (isEndSended) { 1568 return; 1569 } 1570 isEndSended = true; 1571 // add rest statis 1572 try { 1573 String strBufferStat = null; 1574 if (bufferStatJsonArray != null) { 1575 strBufferStat = bufferStatJsonArray.toString(); 1576 bufferStatJsonArray = new JSONArray(); 1577 } 1578 1579 if (apmPlayingTimer != null) { 1580 apmPlayingTimer.cancel(); 1581 apmPlayingTimer = null; 1582 } 1583 1584 if (lastStartPlayTime != 0) { 1585 int interval = (int) (System.currentTimeMillis() - lastStartPlayTime) / 1000; 1586 lastStartPlayTime = 0; 1587 this.stayInterval += interval; 1588 } 1589 int playInterval = this.stayInterval; 1590 JSONObject extra = new JSONObject(); 1591 extra.put("playInterval", playInterval); 1592 extra.put("buffering", new JSONArray(strBufferStat)); 1593 APMEventHandle.getInstance(appContext).onUserPlayEnd(extra); 1594 this.stayInterval = 0; 1595 } catch (Exception e) { 1596 Log.d(TAG, "APMEventHandle exception:" + e.getMessage()); 1597 } 1598 1599 } 1600 1601// /** 1602// * stop or release? 1603// */ 1604// protected void onStopStat() { 1605// if (apmPlayingTimer!= null) { 1606// apmPlayingTimer.cancel(); 1607// apmPlayingTimer = null; 1608// } 1609// } 1610 1611 protected void onInitingStat() { 1612 lastStartPlayTime = System.currentTimeMillis(); 1613 // start a session 1614 startPrepareTimeForApm = System.currentTimeMillis(); 1615 APMEventHandle.getInstance(appContext).updateSession(this.mDataSource); 1616 APMEventHandle.getInstance(appContext).setPlayerRelated(SDK_VERSION, "hw", mAK); 1617 if (mApmEventPlayerCreated) { 1618 APMEventHandle.getInstance(appContext).onPlayerCreated(); 1619 mApmEventPlayerCreated = false; 1620 } 1621 } 1622 1623 protected void onDnsStat(int duration) { 1624 APMEventHandle.getInstance(appContext).onDnsParsed(duration); 1625 } 1626 1627 protected void onHttpStat(long duration) { 1628 APMEventHandle.getInstance(appContext).onHttpConnected(duration / 1000); 1629 } 1630 1631 protected void onPauseStat() { 1632 if (lastStartPlayTime != 0) { 1633 int interval = (int) (System.currentTimeMillis() - lastStartPlayTime) / 1000; 1634 lastStartPlayTime = 0; 1635 this.stayInterval += interval; 1636 } 1637 // add rest statis 1638 try { 1639 if (apmPlayingTimer != null) { 1640 apmPlayingTimer.cancel(); 1641 apmPlayingTimer = null; 1642 } 1643 APMEventHandle.getInstance(appContext).onUserPause(getCurrentPosition() / 1000f); 1644 } catch (Exception e) { 1645 Log.d(TAG, "APMEventHandle exception:" + e.getMessage()); 1646 } 1647 } 1648 1649 protected void onResumeStat() { 1650 isEndSended = false; 1651 if (lastStartPlayTime == 0L) { 1652 lastStartPlayTime = System.currentTimeMillis(); 1653 } 1654 try { 1655 if (apmPlayingTimer != null) { 1656 apmPlayingTimer.cancel(); 1657 apmPlayingTimer = null; 1658 } 1659 apmPlayingTimer = new Timer(); 1660 apmPlayingTimer.schedule(new TimerTask() { 1661 @Override 1662 public void run() { 1663 try { 1664 APMEventHandle.getInstance(appContext).onPlayCount(); 1665 long speed = getDownloadSpeed(); 1666 if (speed > 0) { 1667 APMEventHandle.getInstance(appContext).onNetworkSpeedReport((int) speed); 1668 } 1669 } catch (Exception e) { 1670 Log.d(TAG, "APMEventHandle exception:" + e.getMessage()); 1671 } 1672 } 1673 }, 0, 60 * 1000); 1674 } catch (Exception e) { 1675 Log.d(TAG, "" + e.getMessage()); 1676 } 1677 } 1678 1679 protected void onSeekToStat(long curPositionInSeconds, long seekToInSeconds) { 1680 // add rest statis 1681 try { 1682 JSONObject extra = new JSONObject(); 1683 extra.put("from", curPositionInSeconds); 1684 extra.put("to", seekToInSeconds); 1685 APMEventHandle.getInstance(appContext).onUserSeek(extra); 1686 } catch (Exception e) { 1687 Log.d(TAG, "APMEventHandle exception:" + e.getMessage()); 1688 } 1689 } 1690 1691 protected void onPreparedStat() { 1692 // add rest statis 1693 try { 1694 if (startPrepareTimeForApm > 0L) { 1695 APMEventHandle.getInstance(appContext).onFirstBufferEnd((long) (System.currentTimeMillis() 1696 - startPrepareTimeForApm)); 1697 startPrepareTimeForApm = 0L; 1698 } 1699 // get cdn-ip 1700 String cdnIp = this.getCdnIp(); 1701 if (cdnIp != null && !cdnIp.equals("")) { 1702 APMEventHandle.getInstance(appContext).onUpdateCdn(cdnIp); 1703 } 1704 JSONObject extra = new JSONObject(); 1705 extra.put("videoUrl", this.mDataSource); 1706 extra.put("videoWidth", this.getVideoWidth()); 1707 extra.put("videoHeight", this.getVideoHeight()); 1708 extra.put("playerWidth", 0); // param is deprecated;new sdk is target for mediaplayer, not view 1709 extra.put("playerHeight", 0); // param is deprecated;new sdk is target for mediaplayer, not view 1710 extra.put("duration", this.getDuration()); 1711 JSONObject settings = new JSONObject(); 1712 settings.put("maxCacheSize", maxCacheSizeForApm); 1713 settings.put("cachePauseTime", cachePauseTimeForApm); 1714 settings.put("firstBufferingTime", firstBufferingTimeForApm); 1715 settings.put("toggleFrameChasing", toggleFrameChasingForApm); 1716 settings.put("decodeMode", decodeMode); 1717 settings.put("playbackRate", getSpeed(0f)); 1718 settings.put("enableLooping", isLooping()); 1719 extra.put("settings", settings); 1720 APMEventHandle.getInstance(appContext).onUserPlay(extra); 1721 } catch (Exception e) { 1722 Log.d(TAG, "APMEventHandle exception:" + e.getMessage()); 1723 } 1724 } 1725 1726 protected void onMetadataStat(String playId) { 1727 APMEventHandle.getInstance(appContext).onUpdateMetadata(playId); 1728 } 1729 1730 protected void onInfoStat(int what, int extra) { 1731 try { 1732 if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_START) { 1733 latestBufferStartTime = new Date(); 1734 bufferingStartPosition = getCurrentPosition(); 1735 1736 APMEventHandle.getInstance(appContext).onBufferingStart(); 1737 } else if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_END) { 1738 APMEventHandle.getInstance(appContext).onBufferingEnd(); 1739 1740 if (this.latestBufferStartTime != null) { 1741 Date endTime = new Date(); 1742 JSONObject json = new JSONObject(); 1743 json.put("startPosition", bufferingStartPosition / 1000f); 1744 json.put("endPosition", getCurrentPosition() / 1000f); 1745 json.put("startTimestamp", latestBufferStartTime.getTime()); 1746 json.put("endTimestamp", endTime.getTime()); 1747 bufferStatJsonArray.put(json); 1748 latestBufferStartTime = null; 1749 bufferingStartPosition = -1; 1750 } 1751 } else if (what == IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { 1752 APMEventHandle.getInstance(appContext).onFirstFrameRendered(lastStartPlayTime); 1753 } else if (what == IMediaPlayer.MEDIA_INFO_FRAMECHASING_START) { 1754 APMEventHandle.getInstance(appContext).onFrameChasingStart(); 1755 } else if (what == IMediaPlayer.MEDIA_INFO_FRAMECHASING_END) { 1756 APMEventHandle.getInstance(appContext).onFrameCharingEnd(); 1757 } 1758 } catch (Exception e) { 1759 Log.d(TAG, "buffer stat exception :" + e.getMessage()); 1760 } 1761 } 1762 1763 private static class EventHandler extends Handler { 1764 private final WeakReference<BDCloudMediaPlayer> mWeakPlayer; 1765 1766 public EventHandler(BDCloudMediaPlayer mp, Looper looper) { 1767 super(looper); 1768 mWeakPlayer = new WeakReference<BDCloudMediaPlayer>(mp); 1769 } 1770 1771 @Override 1772 public void handleMessage(Message msg) { 1773 BDCloudMediaPlayer player = mWeakPlayer.get(); 1774 if (player == null || player.mNativeMediaPlayer == 0) { 1775 DebugLog.w(TAG, "BDCloudMediaPlayer went away with unhandled events"); 1776 return; 1777 } 1778 1779 switch (msg.what) { 1780 case MEDIA_PREPARED: 1781 // selectResolutionByIndex may change option seek-at-start, reset here 1782 player.setOption(OPT_CATEGORY_PLAYER, "seek-at-start", 0); 1783 player.onPreparedStat(); 1784 player.notifyOnPrepared(); 1785 return; 1786 1787 case MEDIA_PLAYBACK_COMPLETE: 1788 player.stayAwake(false); 1789 player.onCompleteStat(msg.arg1, msg.arg2); 1790 player.onEndStat(); 1791 player.notifyOnCompletion(); 1792 return; 1793 1794 case MEDIA_BUFFERING_UPDATE: 1795 long bufferPosition = msg.arg1; 1796 if (bufferPosition < 0) { 1797 bufferPosition = 0; 1798 } 1799 1800 long percent = 0; 1801 long duration = player.getDuration(); 1802 if (duration > 0 ) { 1803 if((duration - bufferPosition) >= 1000) { 1804 percent = bufferPosition * 100 / duration; 1805 } else { 1806 percent = (bufferPosition + 1000) * 100 /duration; 1807 } 1808 } 1809 1810 if (percent >= 100) { 1811 percent = 100; 1812 } 1813 1814 // DebugLog.efmt(TAG, "Buffer (%d%%) %d/%d", percent, 1815 // bufferPosition, duration); 1816 player.notifyOnBufferingUpdate((int) percent); 1817 return; 1818 1819 case MEDIA_SEEK_COMPLETE: 1820 player.notifyOnSeekComplete(); 1821 return; 1822 1823 case MEDIA_SET_VIDEO_SIZE: 1824 player.mVideoWidth = msg.arg1; 1825 player.mVideoHeight = msg.arg2; 1826 player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight, player.mVideoSarNum, 1827 player.mVideoSarDen); 1828 return; 1829 1830 case MEDIA_ERROR: 1831 DebugLog.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")"); 1832 player.onErrorStat(msg.arg1, msg.arg2); 1833 player.onEndStat(); 1834 if (!player.notifyOnError(msg.arg1, msg.arg2)) { 1835 player.notifyOnCompletion(); 1836 } 1837 player.stayAwake(false); 1838 return; 1839 case MEDIA_PLAYBACK_STOPPED: 1840 return; 1841 case MEDIA_INFO: 1842 switch (msg.arg1) { 1843 case MEDIA_INFO_VIDEO_RENDERING_START: 1844 DebugLog.i(TAG, "Info: MEDIA_INFO_VIDEO_RENDERING_START\n"); 1845 break; 1846 case MEDIA_INFO_MEDIA_CHANGE_START: 1847 DebugLog.i(TAG, "Info: MEDIA_INFO_MEDIA_CHANGE_START\n"); 1848 break; 1849 case MEDIA_INFO_MEDIA_CHANGE_END: 1850 DebugLog.i(TAG, "Info: MEDIA_INFO_MEDIA_CHANGE_END : "+ msg.arg1 +"\n"); 1851 break; 1852 } 1853 player.onInfoStat(msg.arg1, msg.arg2); 1854 player.notifyOnInfo(msg.arg1, msg.arg2); 1855 // No real default action so far. 1856 return; 1857 case MEDIA_TIMED_TEXT: 1858 if (msg.obj == null) { 1859 player.notifyOnTimedText(null); 1860 } else { 1861 BDTimedText text = new BDTimedText(new Rect(0, 0, 1, 1), (String) msg.obj); 1862 player.notifyOnTimedText(text); 1863 } 1864 return; 1865 case MEDIA_NOP: // interface test message - ignore 1866 break; 1867 1868 case MEDIA_SET_VIDEO_SAR: 1869 player.mVideoSarNum = msg.arg1; 1870 player.mVideoSarDen = msg.arg2; 1871 player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight, player.mVideoSarNum, 1872 player.mVideoSarDen); 1873 break; 1874 1875 case AVAPP_EVENT_METADATA: 1876 Bundle meta = (Bundle) msg.obj; 1877 player.notifyOnMetadata(meta); 1878 for (String key : meta.keySet()) { 1879 if(key.equals("playID") && !(meta.get(key).equals(""))) { 1880 player.onMetadataStat(meta.getString(key)); 1881 playId = meta.getString(key); 1882 } 1883 } 1884 break; 1885 case AVAPP_EVENT_DNS: 1886 player.onDnsStat(msg.arg1); 1887 break; 1888 case AVAPP_EVENT_HTTP_CONNECT_END: 1889 player.onHttpStat(msg.arg1); 1890 break; 1891 1892 default: 1893 DebugLog.e(TAG, "Unknown message type " + msg.what); 1894 } 1895 } 1896 } 1897 1898 /* 1899 * Called from native code when an interesting event happens. This method just uses the EventHandler system to post 1900 * the event back to the main app thread. We use a weak reference to the original BDCloudMediaPlayer object so that 1901 * the native code is safe from the object disappearing from underneath it. (This is the cookie passed to 1902 * native_setup().) 1903 */ 1904 @CalledByNative 1905 private static void postEventFromNative(Object weakThiz, int what, int arg1, int arg2, Object obj) { 1906 if (weakThiz == null) { 1907 return; 1908 } 1909 1910 @SuppressWarnings("rawtypes") 1911 BDCloudMediaPlayer mp = (BDCloudMediaPlayer) ((WeakReference) weakThiz).get(); 1912 if (mp == null) { 1913 return; 1914 } 1915 1916 if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) { 1917 // this acquires the wakelock if needed, and sets the client side 1918 // state 1919 mp.start(); 1920 } 1921 if (mp.mEventHandler != null) { 1922 Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj); 1923 mp.mEventHandler.sendMessage(m); 1924 } 1925 } 1926 1927 /* 1928 * ControlMessage 1929 */ 1930 1931 private OnControlMessageListener mOnControlMessageListener; 1932 1933 private void setOnControlMessageListener(OnControlMessageListener listener) { 1934 mOnControlMessageListener = listener; 1935 } 1936 1937 private interface OnControlMessageListener { 1938 String onControlResolveSegmentUrl(int segment); 1939 } 1940 1941 /* 1942 * NativeInvoke 1943 */ 1944 1945 private OnNativeInvokeListener mOnNativeInvokeListener; 1946 1947 private void setOnNativeInvokeListener(OnNativeInvokeListener listener) { 1948 mOnNativeInvokeListener = listener; 1949 } 1950 1951 private interface OnNativeInvokeListener { 1952 1953 int CTRL_WILL_TCP_OPEN = 0x20001; // NO ARGS 1954 int CTRL_DID_TCP_OPEN = 0x20002; // ARG_ERROR, ARG_FAMILIY, ARG_IP, 1955 // ARG_PORT, ARG_FD 1956 1957 int CTRL_WILL_HTTP_OPEN = 0x20003; // ARG_URL, ARG_SEGMENT_INDEX, 1958 // ARG_RETRY_COUNTER 1959 int CTRL_WILL_LIVE_OPEN = 0x20005; // ARG_URL, ARG_RETRY_COUNTER 1960 int CTRL_WILL_CONCAT_RESOLVE_SEGMENT = 0x20007; // ARG_URL, 1961 // ARG_SEGMENT_INDEX, 1962 // ARG_RETRY_COUNTER 1963 1964 int EVENT_WILL_HTTP_OPEN = 0x1; // ARG_URL 1965 int EVENT_DID_HTTP_OPEN = 0x2; // ARG_URL, ARG_ERROR, ARG_HTTP_CODE 1966 int EVENT_WILL_HTTP_SEEK = 0x3; // ARG_URL, ARG_OFFSET 1967 int EVENT_DID_HTTP_SEEK = 0x4; // ARG_URL, ARG_OFFSET, ARG_ERROR, 1968 // ARG_HTTP_CODE 1969 1970 String ARG_URL = "url"; 1971 String ARG_SEGMENT_INDEX = "segment_index"; 1972 String ARG_RETRY_COUNTER = "retry_counter"; 1973 1974 String ARG_ERROR = "error"; 1975 String ARG_FAMILIY = "family"; 1976 String ARG_IP = "ip"; 1977 String ARG_PORT = "port"; 1978 String ARG_FD = "fd"; 1979 1980 String ARG_OFFSET = "offset"; 1981 String ARG_HTTP_CODE = "http_code"; 1982 1983 /* 1984 * @return true if invoke is handled 1985 * 1986 * @throws Exception on any error 1987 */ 1988 boolean onNativeInvoke(int what, Bundle args); 1989 } 1990 1991 @CalledByNative 1992 private static boolean onNativeInvoke(Object weakThiz, int what, Bundle args) { 1993 DebugLog.ifmt(TAG, "onNativeInvoke %x", what); 1994 if (weakThiz == null || !(weakThiz instanceof WeakReference<?>)) { 1995 throw new IllegalStateException("<null weakThiz>.onNativeInvoke()"); 1996 } 1997 1998 @SuppressWarnings("unchecked") 1999 WeakReference<BDCloudMediaPlayer> weakPlayer = (WeakReference<BDCloudMediaPlayer>) weakThiz; 2000 BDCloudMediaPlayer player = weakPlayer.get(); 2001 if (player == null) { 2002 throw new IllegalStateException("<null weakPlayer>.onNativeInvoke()"); 2003 } 2004 2005 OnNativeInvokeListener listener = player.mOnNativeInvokeListener; 2006 if (listener != null && listener.onNativeInvoke(what, args)) { 2007 return true; 2008 } 2009 2010 switch (what) { 2011 case OnNativeInvokeListener.CTRL_WILL_CONCAT_RESOLVE_SEGMENT: { 2012 OnControlMessageListener onControlMessageListener = player.mOnControlMessageListener; 2013 if (onControlMessageListener == null) { 2014 return false; 2015 } 2016 2017 int segmentIndex = args.getInt(OnNativeInvokeListener.ARG_SEGMENT_INDEX, -1); 2018 if (segmentIndex < 0) { 2019 throw new InvalidParameterException("onNativeInvoke(invalid segment index)"); 2020 } 2021 2022 String newUrl = onControlMessageListener.onControlResolveSegmentUrl(segmentIndex); 2023 if (newUrl == null) { 2024 throw new RuntimeException(new IOException("onNativeInvoke() = <NULL newUrl>")); 2025 } 2026 2027 args.putString(OnNativeInvokeListener.ARG_URL, newUrl); 2028 return true; 2029 } 2030 default: 2031 return false; 2032 } 2033 } 2034 2035 /** 2036 * 自己指定codec,一般不需要设置 2037 */ 2038 private interface OnMediaCodecSelectListener { 2039 String onMediaCodecSelect(IMediaPlayer mp, String mimeType, int profile, int level); 2040 } 2041 2042 private OnMediaCodecSelectListener mOnMediaCodecSelectListener; 2043 2044 /** 2045 * 设置codec解码器,一般不需要设置 2046 * 2047 * @param listener 2048 */ 2049 private void setOnMediaCodecSelectListener(OnMediaCodecSelectListener listener) { 2050 mOnMediaCodecSelectListener = listener; 2051 } 2052 2053 public void resetListeners() { 2054 super.resetListeners(); 2055 mOnMediaCodecSelectListener = null; 2056 } 2057 2058 private boolean isLocalFilePath(String strPath) { 2059 2060 if (null == strPath) { 2061 return false; 2062 } 2063 2064 if (strPath.startsWith("file://") || strPath.startsWith("/")) { 2065 return true; 2066 } 2067 2068 return false; 2069 } 2070 2071 @CalledByNative 2072 private static String onSelectCodec(Object weakThiz, String mimeType, int profile, int level) { 2073 if (weakThiz == null || !(weakThiz instanceof WeakReference<?>)) { 2074 return null; 2075 } 2076 2077 @SuppressWarnings("unchecked") 2078 WeakReference<BDCloudMediaPlayer> weakPlayer = (WeakReference<BDCloudMediaPlayer>) weakThiz; 2079 BDCloudMediaPlayer player = weakPlayer.get(); 2080 if (player == null) { 2081 return null; 2082 } 2083 2084 OnMediaCodecSelectListener listener = player.mOnMediaCodecSelectListener; 2085 if (listener == null) { 2086 listener = DefaultMediaCodecSelector.sInstance; 2087 } 2088 2089 return listener.onMediaCodecSelect(player, mimeType, profile, level); 2090 } 2091 2092 private static class DefaultMediaCodecSelector implements OnMediaCodecSelectListener { 2093 public static final DefaultMediaCodecSelector sInstance = new DefaultMediaCodecSelector(); 2094 2095 @SuppressWarnings("deprecation") 2096 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 2097 public String onMediaCodecSelect(IMediaPlayer mp, String mimeType, int profile, int level) { 2098 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { 2099 return null; 2100 } 2101 2102 if (TextUtils.isEmpty(mimeType)) { 2103 return null; 2104 } 2105 2106 DebugLog.i(TAG, 2107 String.format(Locale.US, "onSelectCodec: mime=%s, profile=%d, level=%d", mimeType, profile, level)); 2108 ArrayList<BDCloudMediaCodecInfo> candidateCodecList = new ArrayList<BDCloudMediaCodecInfo>(); 2109 int numCodecs = MediaCodecList.getCodecCount(); 2110 for (int i = 0; i < numCodecs; i++) { 2111 MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); 2112 DebugLog.d(TAG, String.format(Locale.US, " found codec: %s", codecInfo.getName())); 2113 if (codecInfo.isEncoder()) { 2114 continue; 2115 } 2116 2117 String[] types = codecInfo.getSupportedTypes(); 2118 if (types == null) { 2119 continue; 2120 } 2121 2122 for (String type : types) { 2123 if (TextUtils.isEmpty(type)) { 2124 continue; 2125 } 2126 2127 DebugLog.d(TAG, String.format(Locale.US, " mime: %s", type)); 2128 if (!type.equalsIgnoreCase(mimeType)) { 2129 continue; 2130 } 2131 2132 BDCloudMediaCodecInfo candidate = BDCloudMediaCodecInfo.setupCandidate(codecInfo, mimeType); 2133 if (candidate == null) { 2134 continue; 2135 } 2136 2137 candidateCodecList.add(candidate); 2138 DebugLog.i(TAG, String.format(Locale.US, "candidate codec: %s rank=%d", codecInfo.getName(), 2139 candidate.mRank)); 2140 candidate.dumpProfileLevels(mimeType); 2141 } 2142 } 2143 2144 if (candidateCodecList.isEmpty()) { 2145 return null; 2146 } 2147 2148 BDCloudMediaCodecInfo bestCodec = candidateCodecList.get(0); 2149 2150 for (BDCloudMediaCodecInfo codec : candidateCodecList) { 2151 if (codec.mRank > bestCodec.mRank) { 2152 bestCodec = codec; 2153 } 2154 } 2155 2156 if (bestCodec.mRank < BDCloudMediaCodecInfo.RANK_LAST_CHANCE) { 2157 Log.w(TAG, String.format(Locale.US, "unaccetable codec: %s", bestCodec.mCodecInfo.getName())); 2158 return null; 2159 } 2160 2161 DebugLog.i(TAG, String.format(Locale.US, "selected codec: %s rank=%d", bestCodec.mCodecInfo.getName(), 2162 bestCodec.mRank)); 2163 return bestCodec.mCodecInfo.getName(); 2164 } 2165 } 2166 2167 private static native void native_profileBegin(String libName); 2168 2169 private static native void native_profileEnd(); 2170 2171 private static native void native_setLogLevel(int level); 2172}