MediaPlayer 播放网络音频踩坑记录

  MediaPlayer 错误码

  在最近项目中使用MediaPlayer发现在业务逻辑复杂一些的情况下,使用Mediaplayer频繁重复播放音频的情况下极其容易崩溃,并且出现了无法播放网络音频资源的情况,但是回想之前其他项目中使用MediaPlayer播放网络短音视频资源没有出现过这种问题,感到十分困惑,于是决定重新开始一探究竟。

  需要解决两个问题:

  1. MediaPlayer API安全调用,避免崩溃;
  2. 网络音频无法播放问题;

  官方建议在使用时候要捕获异常,但是在考虑了Mediaplayer整体生命周期之后,MediaPlayer的API必须要在相应的状态下才能调用,调用后会产生新的状态,因此更好的方案应该通过记录当前播放状态来达到安全调用的目的避免崩溃,而不是放任异常然后去捕获,但是捕获异常仍然可以作为辅助措施以防应用崩溃。另外在MediaPlayer生命周期内API调用异常的情况下也会导致播放器不断重试加载资源而无法播放的情况,需要周详考虑。

  网上已经有比较好的生命周期的播放官方资料的翻译,我就不多赘言。
   MediaPlayer 状态机 API 详解 示例
   Android Developer MediaPlayer

  在实现过程中发现MediaPlayer一直无法播放网络音频地址http://music.163.com/song/media/outer/url?id=317151.mp3,并且输出如下log:

2019-07-20 18:10:42.194 29460-29460/com.khronos.issue I/MediaPlayer: constructor
2019-07-20 18:10:42.206 29460-29460/com.khronos.issue W/MediaPlayer: Use of stream types is deprecated for operations other than volume control
2019-07-20 18:10:42.206 29460-29460/com.khronos.issue W/MediaPlayer: See the documentation of setAudioStreamType() for what to use instead with android.media.AudioAttributes to qualify your playback use case
2019-07-20 18:10:42.216 29460-29460/com.khronos.issue V/MediaPlayer: resetDrmState:  mDrmInfo=null mDrmProvisioningThread=null mPrepareDrmInProgress=false mActiveDrmScheme=false
2019-07-20 18:10:42.216 29460-29460/com.khronos.issue V/MediaPlayer: cleanDrmObj: mDrmObj=null mDrmSessionId=null
2019-07-20 18:10:42.218 29460-29460/com.khronos.issue I/MediaPlayer: constructor
2019-07-20 18:10:42.224 29460-29460/com.khronos.issue W/MediaPlayer: Use of stream types is deprecated for operations other than volume control
2019-07-20 18:10:42.224 29460-29460/com.khronos.issue W/MediaPlayer: See the documentation of setAudioStreamType() for what to use instead with android.media.AudioAttributes to qualify your playback use case
2019-07-20 18:10:42.228 29460-29460/com.khronos.issue W/m.khronos.issu: Accessing hidden method Landroid/media/MediaPlayer;->setDataSource(Ljava/lang/String;Ljava/util/Map;)V (light greylist, reflection)
2019-07-20 18:10:42.228 29460-29460/com.khronos.issue I/MediaPlayer: setDataSource:http://music.163.com/song/media/outer/url?id=317151.mp3
2019-07-20 18:10:42.228 29460-29460/com.khronos.issue V/MediaHTTPService: MediaHTTPService(android.media.MediaHTTPService@aa4f989): Cookies: null
2019-07-20 18:10:42.230 932-18904/? D/MediaPlayerService: LF player type = 4
2019-07-20 18:10:42.231 932-18904/? D/NuPlayerDriver: NuPlayerDriver(0xe5921c00) created, clientPid(29460)
2019-07-20 18:10:42.235 29460-29540/com.khronos.issue V/MediaHTTPService: makeHTTPConnection: CookieManager created: java.net.CookieManager@53c2d8e
2019-07-20 18:10:42.235 29460-29540/com.khronos.issue V/MediaHTTPService: makeHTTPConnection(android.media.MediaHTTPService@aa4f989): cookieHandler: java.net.CookieManager@53c2d8e Cookies: null
2019-07-20 18:10:42.241 29460-29540/com.khronos.issue I/DpmTcmClient: RegisterTcmMonitor from: com.android.okhttp.TcmIdleTimerMonitor
2019-07-20 18:10:42.249 2894-2908/? V/OPConfig:ConfigProvider: Module:GameModeImComponents
2019-07-20 18:10:42.250 29460-29540/com.khronos.issue D/NetworkSecurityConfig: Using Network Security Config from resource xms_network_security_config debugBuild: true
2019-07-20 18:10:42.261 932-29605/? E/NuCachedSource2: source returned error -1, 10 retries left
2019-07-20 18:10:42.267 29460-29603/com.khronos.issue E/Config:Grabber:GameModeImComponents: Exception:Value de53f99085b83be13e7d7ae3c37d9528177b49f73c95e86886404ffe9b8c0855cfbdfcfc85713ea2cdf2e7aa44b0c5d2b15e362dfc78808a11ddb82d823c928e5bf5270d46222c74ee528e5c19b72c0746b7f31bf949bec78c03edd7da2975d623e552f35f5a36f80c93cd2e11337c2c82c8eaec42f4a5304fe97742db7739f3ec1a7afe40bab7e3d879f2d7989a153a3eaef8078a4a0aa80182629b887e6ced525cff4910cbc27572f536f23274eaf112df11d5fb23fb79a0e30e3ceefbd8a803e2c30743d02d869a2a2b64ac9ea0304321299381029bc541f6e7c251b08b339000ee2695eb6441ca5a11069442b4ca38b1c970b907b59cc8cc307218c80ff88a1289a56fe9b934b6157e82a018bd1f1abafa66f24f07181db49eff9dde5ec3905d241e714bb2adfdde1dfbe60e9a3dacebccedb3810fc79d7adff30964a10adcf0305cbfa598da92b6d866f955479d010c99808a66f9c550b80ef7cdb23c9d212fc1321fa5ded0916f3e29ca549fc5fa4f7b49c5dfe62ea2af0e3e9ba61ecfd84a85886747941cc18bd1994e12afbf1db2be1547ee43f5a29efdc18b4c9f27d66b55a8c19eef7f876e95d1020d89dabcf0316c4662e363e10430a905455f76032a32c01043fd60af08203d605ec55e03eb8fee26c533fee7a93f05dbce244c5aa975cb666a633532654549a6685941d39211e70d8c35e12a993c3a4ede634eba805ae47c972fd921f55ccad345a1e878ac37bf37e031c3cdc5d02618a8d9c99ac23b204ee7212ad09df11f835c4dcf10c36d2a76ae0bfce5b1715bd4191f7a8a819817311f53b9ac415839503a9e56137d2a0f4eabe1ab482d9863768da460cb1ad19c22a66174e1119f06358de4303a6f3ab734235b27ff6070cc1a059820a770cfa7f289f8565e734222b5d9673cd606a6012b88bd26274ef3479f4606a411589a190421e4f2af2889d69c2a047a64d7168b1b0f645753b43b5cee97b3bc05b0da5926f00d57b6dba6334bb56dd28ca0d6837d024b40d4d52c2f3932a148978f12f93cd590f86e171fb1e3ca6c478559709c0a33759c9aae4eff65bd398f99200f108cc64a9445fafe4218fe6ff6196a66820ac6a37906f063476aa983ea62abf5a64c8f01e8a9a6ce44b3f4086156a22bf4258c616eacfe97faa25801dec717321be07e43c341fbbddce894ce50f5de06b1d2cc5202ac8f581e64eafaea4b980a67acda79ebdaa52fc9e8230ee3c94b7e45bb9e33bb7252dfb9289779f5eef7477ac80ed948f2a34e661196fc692be434dcfb854adac5304016b0c97cf23f324e4399be706d015682f5ecf2f66b45dff468d425b81cb1a346d01a8f606abfc632ad4e6035c26dc09ef9002f64ba071c9c8dc4fbb8735b8665c0e43510024e2cdeff8b50b3bda3773967365f97314a9a1c78f19c3c71e1c84138a704539264419e8a8d9f1c3c2c620f870dd497c36981dae8983397a36399d1b1805263df98506e4e35a96b751c28de839140f0d26e58356cc3203d322a1fc0fc3b47e9deca38da35774cb1364ea7d7800282dbb87cde710ed88aeb3022678c96ce346ce0327a73c35343bb3e890de0bb968e10496b7201eb3e624c74904770af0128752209890c5bba782770b9773498a02d5517429a50f31344998e028583029402b3480fb0f96121c777f180ff35e3d945ec94c6c6c5af8960495a2b71266413a82c756991f7f3dd2a8234f909d9f8a93f4eeb919606e0983a68953a144d97caa42a23b9dfe84bd52c351d262076dec15d5bdf of type java.lang.String cannot be converted to JSONArray
2019-07-20 18:10:42.269 2894-2908/? V/OPConfig:ConfigProvider: Module:GameModeImComponents
2019-07-20 18:10:42.278 29460-29606/com.khronos.issue E/Config:Grabber:GameModeImComponents: Exception:Value de53f99085b83be13e7d7ae3c37d9528177b49f73c95e86886404ffe9b8c0855cfbdfcfc85713ea2cdf2e7aa44b0c5d2b15e362dfc78808a11ddb82d823c928e5bf5270d46222c74ee528e5c19b72c0746b7f31bf949bec78c03edd7da2975d623e552f35f5a36f80c93cd2e11337c2c82c8eaec42f4a5304fe97742db7739f3ec1a7afe40bab7e3d879f2d7989a153a3eaef8078a4a0aa80182629b887e6ced525cff4910cbc27572f536f23274eaf112df11d5fb23fb79a0e30e3ceefbd8a803e2c30743d02d869a2a2b64ac9ea0304321299381029bc541f6e7c251b08b339000ee2695eb6441ca5a11069442b4ca38b1c970b907b59cc8cc307218c80ff88a1289a56fe9b934b6157e82a018bd1f1abafa66f24f07181db49eff9dde5ec3905d241e714bb2adfdde1dfbe60e9a3dacebccedb3810fc79d7adff30964a10adcf0305cbfa598da92b6d866f955479d010c99808a66f9c550b80ef7cdb23c9d212fc1321fa5ded0916f3e29ca549fc5fa4f7b49c5dfe62ea2af0e3e9ba61ecfd84a85886747941cc18bd1994e12afbf1db2be1547ee43f5a29efdc18b4c9f27d66b55a8c19eef7f876e95d1020d89dabcf0316c4662e363e10430a905455f76032a32c01043fd60af08203d605ec55e03eb8fee26c533fee7a93f05dbce244c5aa975cb666a633532654549a6685941d39211e70d8c35e12a993c3a4ede634eba805ae47c972fd921f55ccad345a1e878ac37bf37e031c3cdc5d02618a8d9c99ac23b204ee7212ad09df11f835c4dcf10c36d2a76ae0bfce5b1715bd4191f7a8a819817311f53b9ac415839503a9e56137d2a0f4eabe1ab482d9863768da460cb1ad19c22a66174e1119f06358de4303a6f3ab734235b27ff6070cc1a059820a770cfa7f289f8565e734222b5d9673cd606a6012b88bd26274ef3479f4606a411589a190421e4f2af2889d69c2a047a64d7168b1b0f645753b43b5cee97b3bc05b0da5926f00d57b6dba6334bb56dd28ca0d6837d024b40d4d52c2f3932a148978f12f93cd590f86e171fb1e3ca6c478559709c0a33759c9aae4eff65bd398f99200f108cc64a9445fafe4218fe6ff6196a66820ac6a37906f063476aa983ea62abf5a64c8f01e8a9a6ce44b3f4086156a22bf4258c616eacfe97faa25801dec717321be07e43c341fbbddce894ce50f5de06b1d2cc5202ac8f581e64eafaea4b980a67acda79ebdaa52fc9e8230ee3c94b7e45bb9e33bb7252dfb9289779f5eef7477ac80ed948f2a34e661196fc692be434dcfb854adac5304016b0c97cf23f324e4399be706d015682f5ecf2f66b45dff468d425b81cb1a346d01a8f606abfc632ad4e6035c26dc09ef9002f64ba071c9c8dc4fbb8735b8665c0e43510024e2cdeff8b50b3bda3773967365f97314a9a1c78f19c3c71e1c84138a704539264419e8a8d9f1c3c2c620f870dd497c36981dae8983397a36399d1b1805263df98506e4e35a96b751c28de839140f0d26e58356cc3203d322a1fc0fc3b47e9deca38da35774cb1364ea7d7800282dbb87cde710ed88aeb3022678c96ce346ce0327a73c35343bb3e890de0bb968e10496b7201eb3e624c74904770af0128752209890c5bba782770b9773498a02d5517429a50f31344998e028583029402b3480fb0f96121c777f180ff35e3d945ec94c6c6c5af8960495a2b71266413a82c756991f7f3dd2a8234f909d9f8a93f4eeb919606e0983a68953a144d97caa42a23b9dfe84bd52c351d262076dec15d5bdf of type java.lang.String cannot be converted to JSONArray
2019-07-20 18:10:42.279 2894-2908/? V/OPConfig:ConfigProvider: Module:GameModeImComponents
2019-07-20 18:10:42.288 29460-29607/com.khronos.issue E/Config:Grabber:GameModeImComponents: Exception:Value de53f99085b83be13e7d7ae3c37d9528177b49f73c95e86886404ffe9b8c0855cfbdfcfc85713ea2cdf2e7aa44b0c5d2b15e362dfc78808a11ddb82d823c928e5bf5270d46222c74ee528e5c19b72c0746b7f31bf949bec78c03edd7da2975d623e552f35f5a36f80c93cd2e11337c2c82c8eaec42f4a5304fe97742db7739f3ec1a7afe40bab7e3d879f2d7989a153a3eaef8078a4a0aa80182629b887e6ced525cff4910cbc27572f536f23274eaf112df11d5fb23fb79a0e30e3ceefbd8a803e2c30743d02d869a2a2b64ac9ea0304321299381029bc541f6e7c251b08b339000ee2695eb6441ca5a11069442b4ca38b1c970b907b59cc8cc307218c80ff88a1289a56fe9b934b6157e82a018bd1f1abafa66f24f07181db49eff9dde5ec3905d241e714bb2adfdde1dfbe60e9a3dacebccedb3810fc79d7adff30964a10adcf0305cbfa598da92b6d866f955479d010c99808a66f9c550b80ef7cdb23c9d212fc1321fa5ded0916f3e29ca549fc5fa4f7b49c5dfe62ea2af0e3e9ba61ecfd84a85886747941cc18bd1994e12afbf1db2be1547ee43f5a29efdc18b4c9f27d66b55a8c19eef7f876e95d1020d89dabcf0316c4662e363e10430a905455f76032a32c01043fd60af08203d605ec55e03eb8fee26c533fee7a93f05dbce244c5aa975cb666a633532654549a6685941d39211e70d8c35e12a993c3a4ede634eba805ae47c972fd921f55ccad345a1e878ac37bf37e031c3cdc5d02618a8d9c99ac23b204ee7212ad09df11f835c4dcf10c36d2a76ae0bfce5b1715bd4191f7a8a819817311f53b9ac415839503a9e56137d2a0f4eabe1ab482d9863768da460cb1ad19c22a66174e1119f06358de4303a6f3ab734235b27ff6070cc1a059820a770cfa7f289f8565e734222b5d9673cd606a6012b88bd26274ef3479f4606a411589a190421e4f2af2889d69c2a047a64d7168b1b0f645753b43b5cee97b3bc05b0da5926f00d57b6dba6334bb56dd28ca0d6837d024b40d4d52c2f3932a148978f12f93cd590f86e171fb1e3ca6c478559709c0a33759c9aae4eff65bd398f99200f108cc64a9445fafe4218fe6ff6196a66820ac6a37906f063476aa983ea62abf5a64c8f01e8a9a6ce44b3f4086156a22bf4258c616eacfe97faa25801dec717321be07e43c341fbbddce894ce50f5de06b1d2cc5202ac8f581e64eafaea4b980a67acda79ebdaa52fc9e8230ee3c94b7e45bb9e33bb7252dfb9289779f5eef7477ac80ed948f2a34e661196fc692be434dcfb854adac5304016b0c97cf23f324e4399be706d015682f5ecf2f66b45dff468d425b81cb1a346d01a8f606abfc632ad4e6035c26dc09ef9002f64ba071c9c8dc4fbb8735b8665c0e43510024e2cdeff8b50b3bda3773967365f97314a9a1c78f19c3c71e1c84138a704539264419e8a8d9f1c3c2c620f870dd497c36981dae8983397a36399d1b1805263df98506e4e35a96b751c28de839140f0d26e58356cc3203d322a1fc0fc3b47e9deca38da35774cb1364ea7d7800282dbb87cde710ed88aeb3022678c96ce346ce0327a73c35343bb3e890de0bb968e10496b7201eb3e624c74904770af0128752209890c5bba782770b9773498a02d5517429a50f31344998e028583029402b3480fb0f96121c777f180ff35e3d945ec94c6c6c5af8960495a2b71266413a82c756991f7f3dd2a8234f909d9f8a93f4eeb919606e0983a68953a144d97caa42a23b9dfe84bd52c351d262076dec15d5bdf of type java.lang.String cannot be converted to JSONArray
2019-07-20 18:10:42.293 2894-2908/? V/OPConfig:ConfigProvider: Module:GameModeImComponents
2019-07-20 18:10:42.302 29460-29608/com.khronos.issue E/Config:Grabber:GameModeImComponents: Exception:Value de53f99085b83be13e7d7ae3c37d9528177b49f73c95e86886404ffe9b8c0855cfbdfcfc85713ea2cdf2e7aa44b0c5d2b15e362dfc78808a11ddb82d823c928e5bf5270d46222c74ee528e5c19b72c0746b7f31bf949bec78c03edd7da2975d623e552f35f5a36f80c93cd2e11337c2c82c8eaec42f4a5304fe97742db7739f3ec1a7afe40bab7e3d879f2d7989a153a3eaef8078a4a0aa80182629b887e6ced525cff4910cbc27572f536f23274eaf112df11d5fb23fb79a0e30e3ceefbd8a803e2c30743d02d869a2a2b64ac9ea0304321299381029bc541f6e7c251b08b339000ee2695eb6441ca5a11069442b4ca38b1c970b907b59cc8cc307218c80ff88a1289a56fe9b934b6157e82a018bd1f1abafa66f24f07181db49eff9dde5ec3905d241e714bb2adfdde1dfbe60e9a3dacebccedb3810fc79d7adff30964a10adcf0305cbfa598da92b6d866f955479d010c99808a66f9c550b80ef7cdb23c9d212fc1321fa5ded0916f3e29ca549fc5fa4f7b49c5dfe62ea2af0e3e9ba61ecfd84a85886747941cc18bd1994e12afbf1db2be1547ee43f5a29efdc18b4c9f27d66b55a8c19eef7f876e95d1020d89dabcf0316c4662e363e10430a905455f76032a32c01043fd60af08203d605ec55e03eb8fee26c533fee7a93f05dbce244c5aa975cb666a633532654549a6685941d39211e70d8c35e12a993c3a4ede634eba805ae47c972fd921f55ccad345a1e878ac37bf37e031c3cdc5d02618a8d9c99ac23b204ee7212ad09df11f835c4dcf10c36d2a76ae0bfce5b1715bd4191f7a8a819817311f53b9ac415839503a9e56137d2a0f4eabe1ab482d9863768da460cb1ad19c22a66174e1119f06358de4303a6f3ab734235b27ff6070cc1a059820a770cfa7f289f8565e734222b5d9673cd606a6012b88bd26274ef3479f4606a411589a190421e4f2af2889d69c2a047a64d7168b1b0f645753b43b5cee97b3bc05b0da5926f00d57b6dba6334bb56dd28ca0d6837d024b40d4d52c2f3932a148978f12f93cd590f86e171fb1e3ca6c478559709c0a33759c9aae4eff65bd398f99200f108cc64a9445fafe4218fe6ff6196a66820ac6a37906f063476aa983ea62abf5a64c8f01e8a9a6ce44b3f4086156a22bf4258c616eacfe97faa25801dec717321be07e43c341fbbddce894ce50f5de06b1d2cc5202ac8f581e64eafaea4b980a67acda79ebdaa52fc9e8230ee3c94b7e45bb9e33bb7252dfb9289779f5eef7477ac80ed948f2a34e661196fc692be434dcfb854adac5304016b0c97cf23f324e4399be706d015682f5ecf2f66b45dff468d425b81cb1a346d01a8f606abfc632ad4e6035c26dc09ef9002f64ba071c9c8dc4fbb8735b8665c0e43510024e2cdeff8b50b3bda3773967365f97314a9a1c78f19c3c71e1c84138a704539264419e8a8d9f1c3c2c620f870dd497c36981dae8983397a36399d1b1805263df98506e4e35a96b751c28de839140f0d26e58356cc3203d322a1fc0fc3b47e9deca38da35774cb1364ea7d7800282dbb87cde710ed88aeb3022678c96ce346ce0327a73c35343bb3e890de0bb968e10496b7201eb3e624c74904770af0128752209890c5bba782770b9773498a02d5517429a50f31344998e028583029402b3480fb0f96121c777f180ff35e3d945ec94c6c6c5af8960495a2b71266413a82c756991f7f3dd2a8234f909d9f8a93f4eeb919606e0983a68953a144d97caa42a23b9dfe84bd52c351d262076dec15d5bdf of type java.lang.String cannot be converted to JSONArray
    
    --------- beginning of system
2019-07-20 18:10:43.894 1318-2336/? V/AlarmManager: Triggering alarm #0: 2 when =75078454 package =android operation = null listenTag =*job.delay* flags =0x0
2019-07-20 18:10:43.911 29547-29547/com.khronos.issue.MyJobService E/Job:  onStartJob com.khronos.issue.job.schedule.MyJobService@3a91916
2019-07-20 18:10:43.916 29519-29519/com.khronos.issue:nfls E/Job:  onStartCommand com.khronos.issue.job.KeepAliveService@3a91916
2019-07-20 18:10:45.162 611-611/? W/auditd: type=1400 "CokaNew-4""loadavg""proc"
2019-07-20 18:10:45.287 932-29605/? E/NuCachedSource2: source returned error -1, 9 retries left
2019-07-20 18:10:46.061 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:10:46.063 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:10:46.792 2482-2595/? D/NetworkController.MobileSignalController(1): onSignalStrengthsChanged signalStrength=SignalStrength: 99 0 -120 -160 -120 -160 -1 24 -96 -11 138 2147483647 0 2147483647 4 4 99 255 2147483647 gsm|lte use_rsrp_and_rssnr_for_lte_level  [-128, -118, -108, -98] [-115, -105, -95, -85] level=4 voicelevel=4 datalevel=4
2019-07-20 18:10:46.853 2482-2482/? E/ndroid.systemu: Invalid ID 0x00000000.
2019-07-20 18:10:48.312 932-29605/? E/NuCachedSource2: source returned error -1, 8 retries left
2019-07-20 18:10:50.220 611-611/? W/auditd: type=1400 "CokaNew-4""loadavg""proc"
2019-07-20 18:10:51.063 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:10:51.064 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:10:51.322 932-29605/? E/NuCachedSource2: source returned error -1, 7 retries left
2019-07-20 18:10:53.696 1318-2336/? V/AlarmManager: Triggering alarm #0: 2 when =75088254 package =com.eg.android.AlipayGphone operation =*walarm*:ALARM_ACTION(3932) flags =0x1
2019-07-20 18:10:53.730 1318-1377/? W/BroadcastQueue: Background execution not allowed: receiving Intent { act=android.net.conn.DATA_ACTIVITY_CHANGE flg=0x10 (has extras) } to com.oneplus.security/.network.trafficalarm.TrafficUsageAlarmReceiver
2019-07-20 18:10:54.341 932-29605/? E/NuCachedSource2: source returned error -1, 6 retries left
2019-07-20 18:10:55.270 611-611/? W/auditd: type=1400 "CokaNew-4""loadavg""proc"
2019-07-20 18:10:56.064 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:10:56.063 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:10:57.349 932-29605/? E/NuCachedSource2: source returned error -1, 5 retries left
2019-07-20 18:11:00.001 1318-2336/? V/AlarmManager: Triggering alarm #0: 3 when =75094560 package =android operation =*alarm*:android.intent.action.TIME_TICK flags =0x1
2019-07-20 18:11:00.017 3084-3084/? I/DigitalWidgetViewsFactory: getCount() return 0 !!!
2019-07-20 18:11:00.057 2482-2575/? I/ClockCtrl:  schedule next: 59943
2019-07-20 18:11:00.057 2482-2575/? I/KeyguardUpdateMonitor: onTimeChanged
2019-07-20 18:11:00.058 2482-2482/? D/KeyguardUpdateMonitor: handleTimeUpdate
2019-07-20 18:11:00.062 2482-2482/? I/Clock: onTimeChanged
2019-07-20 18:11:00.062 2482-2482/? I/Clock: onTimeChanged
2019-07-20 18:11:00.062 2482-2482/? I/DateView: onTimeChanged
2019-07-20 18:11:00.322 611-611/? W/auditd: type=1400 "CokaNew-4""loadavg""proc"
2019-07-20 18:11:00.368 932-29605/? E/NuCachedSource2: source returned error -1, 4 retries left
2019-07-20 18:11:01.065 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:11:01.065 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:11:03.384 932-29605/? E/NuCachedSource2: source returned error -1, 3 retries left
2019-07-20 18:11:04.019 1318-1377/? W/BroadcastQueue: Background execution not allowed: receiving Intent { act=android.net.conn.DATA_ACTIVITY_CHANGE flg=0x10 (has extras) } to com.oneplus.security/.network.trafficalarm.TrafficUsageAlarmReceiver
2019-07-20 18:11:05.380 611-611/? W/auditd: type=1400 "CokaNew-4""loadavg""proc"
2019-07-20 18:11:06.066 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:11:06.067 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:11:06.409 932-29605/? E/NuCachedSource2: source returned error -1, 2 retries left
2019-07-20 18:11:09.425 932-29605/? E/NuCachedSource2: source returned error -1, 1 retries left
2019-07-20 18:11:10.423 611-611/? W/auditd: type=1400 "CokaNew-4""loadavg""proc"
2019-07-20 18:11:11.070 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:11:11.071 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:11:11.588 1318-1377/? D/ActivityManager: skip: com.tencent.mm
2019-07-20 18:11:11.590 797-797/? D/AudioPolicyService: setRecordSilenced() uid 10021 on silenced 1
2019-07-20 18:11:11.605 797-797/? D/AudioPolicyService: setRecordSilenced() uid 10069 on silenced 1
2019-07-20 18:11:12.442 932-29605/? E/NuCachedSource2: source returned error -1, 0 retries left
2019-07-20 18:11:12.501 932-29602/? E/GenericSource: initFromDataSource, cannot create extractor!
2019-07-20 18:11:12.501 932-29602/? E/GenericSource: Failed to init from data source!
2019-07-20 18:11:12.501 932-29601/? D/NuPlayerDriver: notifyListener_l(0xe5921c00), (100, 1, -2147483648, -1), loop setting(0, 0)
2019-07-20 18:11:12.502 29460-29476/com.khronos.issue E/MediaPlayerNative: error (1, -2147483648)
2019-07-20 18:11:12.503 29460-29460/com.khronos.issue E/MediaPlayer: Error (1,-2147483648)
2019-07-20 18:11:12.506 29460-29460/com.khronos.issue V/MediaPlayer: resetDrmState:  mDrmInfo=null mDrmProvisioningThread=null mPrepareDrmInProgress=false mActiveDrmScheme=false
2019-07-20 18:11:12.506 29460-29460/com.khronos.issue V/MediaPlayer: cleanDrmObj: mDrmObj=null mDrmSessionId=null
2019-07-20 18:11:12.506 932-18904/? D/NuPlayerDriver: reset(0xe5921c00) at state 2
2019-07-20 18:11:12.510 932-18904/? D/NuPlayerDriver: notifyListener_l(0xe5921c00), (8, 0, 0, -1), loop setting(0, 0)
2019-07-20 18:11:12.510 932-29601/? W/AMessage: failed to post message as target looper for handler 0 is gone.
2019-07-20 18:11:12.511 932-29601/? D/NuPlayerDriver: notifyResetComplete(0xe5921c00)
2019-07-20 18:11:12.513 29460-29460/com.khronos.issue E/Khronos-MediaPlayerManager: ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────
2019-07-20 18:11:12.513 29460-29460/com.khronos.issue E/Khronos-MediaPlayerManager: │ Thread: main
2019-07-20 18:11:12.513 29460-29460/com.khronos.issue E/Khronos-MediaPlayerManager: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
2019-07-20 18:11:12.514 29460-29460/com.khronos.issue E/Khronos-MediaPlayerManager: │ onError:mp = android.media.MediaPlayer@8c04fa7, what = 1, extra = -2147483648
2019-07-20 18:11:12.514 29460-29460/com.khronos.issue E/Khronos-MediaPlayerManager: └────────────────────────────────────────────────────────────────────────────────────────────────────────────────
2019-07-20 18:11:11.604 1318-1377/? I/chatty: uid=1000(system) ActivityManager identical 2 lines

在上述日志中有一段如下:

2019-07-20 18:10:42.228 29460-29460/com.khronos.issue I/MediaPlayer: setDataSource:http://music.163.com/song/media/outer/url?id=317151.mp3
2019-07-20 18:10:42.228 29460-29460/com.khronos.issue V/MediaHTTPService: MediaHTTPService(android.media.MediaHTTPService@aa4f989): Cookies: null
2019-07-20 18:10:42.230 932-18904/? D/MediaPlayerService: LF player type = 4
2019-07-20 18:10:42.231 932-18904/? D/NuPlayerDriver: NuPlayerDriver(0xe5921c00) created, clientPid(29460)
2019-07-20 18:10:42.235 29460-29540/com.khronos.issue V/MediaHTTPService: makeHTTPConnection: CookieManager created: java.net.CookieManager@53c2d8e
2019-07-20 18:10:42.235 29460-29540/com.khronos.issue V/MediaHTTPService: makeHTTPConnection(android.media.MediaHTTPService@aa4f989): cookieHandler: java.net.CookieManager@53c2d8e Cookies: null
2019-07-20 18:10:42.241 29460-29540/com.khronos.issue I/DpmTcmClient: RegisterTcmMonitor from: com.android.okhttp.TcmIdleTimerMonitor
2019-07-20 18:10:42.249 2894-2908/? V/OPConfig:ConfigProvider: Module:GameModeImComponents
2019-07-20 18:10:42.250 29460-29540/com.khronos.issue D/NetworkSecurityConfig: Using Network Security Config from resource xms_network_security_config debugBuild: true

日志中显示执行网络请求,并且使用了项目中的网络配置文件,并且执行了9次的资源加载重试

2019-07-20 18:10:45.287 932-29605/? E/NuCachedSource2: source returned error -1, 9 retries left
2019-07-20 18:10:46.061 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:10:46.063 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:10:46.792 2482-2595/? D/NetworkController.MobileSignalController(1): onSignalStrengthsChanged signalStrength=SignalStrength: 99 0 -120 -160 -120 -160 -1 24 -96 -11 138 2147483647 0 2147483647 4 4 99 255 2147483647 gsm|lte use_rsrp_and_rssnr_for_lte_level  [-128, -118, -108, -98] [-115, -105, -95, -85] level=4 voicelevel=4 datalevel=4
2019-07-20 18:10:46.853 2482-2482/? E/ndroid.systemu: Invalid ID 0x00000000.
2019-07-20 18:10:48.312 932-29605/? E/NuCachedSource2: source returned error -1, 8 retries left
2019-07-20 18:10:50.220 611-611/? W/auditd: type=1400 "CokaNew-4""loadavg""proc"
2019-07-20 18:10:51.063 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:10:51.064 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:10:51.322 932-29605/? E/NuCachedSource2: source returned error -1, 7 retries left
2019-07-20 18:10:53.696 1318-2336/? V/AlarmManager: Triggering alarm #0: 2 when =75088254 package =com.eg.android.AlipayGphone operation =*walarm*:ALARM_ACTION(3932) flags =0x1
2019-07-20 18:10:53.730 1318-1377/? W/BroadcastQueue: Background execution not allowed: receiving Intent { act=android.net.conn.DATA_ACTIVITY_CHANGE flg=0x10 (has extras) } to com.oneplus.security/.network.trafficalarm.TrafficUsageAlarmReceiver
2019-07-20 18:10:54.341 932-29605/? E/NuCachedSource2: source returned error -1, 6 retries left
2019-07-20 18:10:55.270 611-611/? W/auditd: type=1400 "CokaNew-4""loadavg""proc"
2019-07-20 18:10:56.064 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:10:56.063 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:10:57.349 932-29605/? E/NuCachedSource2: source returned error -1, 5 retries left
2019-07-20 18:11:00.001 1318-2336/? V/AlarmManager: Triggering alarm #0: 3 when =75094560 package =android operation =*alarm*:android.intent.action.TIME_TICK flags =0x1
2019-07-20 18:11:00.017 3084-3084/? I/DigitalWidgetViewsFactory: getCount() return 0 !!!
2019-07-20 18:11:00.057 2482-2575/? I/ClockCtrl:  schedule next: 59943
2019-07-20 18:11:00.057 2482-2575/? I/KeyguardUpdateMonitor: onTimeChanged
2019-07-20 18:11:00.058 2482-2482/? D/KeyguardUpdateMonitor: handleTimeUpdate
2019-07-20 18:11:00.062 2482-2482/? I/Clock: onTimeChanged
2019-07-20 18:11:00.062 2482-2482/? I/Clock: onTimeChanged
2019-07-20 18:11:00.062 2482-2482/? I/DateView: onTimeChanged
2019-07-20 18:11:00.322 611-611/? W/auditd: type=1400 "CokaNew-4""loadavg""proc"
2019-07-20 18:11:00.368 932-29605/? E/NuCachedSource2: source returned error -1, 4 retries left
2019-07-20 18:11:01.065 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:11:01.065 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:11:03.384 932-29605/? E/NuCachedSource2: source returned error -1, 3 retries left
2019-07-20 18:11:04.019 1318-1377/? W/BroadcastQueue: Background execution not allowed: receiving Intent { act=android.net.conn.DATA_ACTIVITY_CHANGE flg=0x10 (has extras) } to com.oneplus.security/.network.trafficalarm.TrafficUsageAlarmReceiver
2019-07-20 18:11:05.380 611-611/? W/auditd: type=1400 "CokaNew-4""loadavg""proc"
2019-07-20 18:11:06.066 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:11:06.067 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:11:06.409 932-29605/? E/NuCachedSource2: source returned error -1, 2 retries left
2019-07-20 18:11:09.425 932-29605/? E/NuCachedSource2: source returned error -1, 1 retries left
2019-07-20 18:11:10.423 611-611/? W/auditd: type=1400 "CokaNew-4""loadavg""proc"
2019-07-20 18:11:11.070 917-1046/? E/libc: Access denied finding property "sys.thermal.para"
2019-07-20 18:11:11.071 611-611/? W/auditd: type=1400 "thermal-engine""u:object_r:system_prop:s0""tmpfs"
2019-07-20 18:11:11.588 1318-1377/? D/ActivityManager: skip: com.tencent.mm
2019-07-20 18:11:11.590 797-797/? D/AudioPolicyService: setRecordSilenced() uid 10021 on silenced 1
2019-07-20 18:11:11.605 797-797/? D/AudioPolicyService: setRecordSilenced() uid 10069 on silenced 1
2019-07-20 18:11:12.442 932-29605/? E/NuCachedSource2: source returned error -1, 0 retries left

最终加载失败

2019-07-20 18:11:12.501 932-29602/? E/GenericSource: initFromDataSource, cannot create extractor!
2019-07-20 18:11:12.501 932-29602/? E/GenericSource: Failed to init from data source!

  通过google以上日志查找到Android MediaPlayer使用之网络访问异常,顿时惊醒!!!我使用的网络音频地址是http地址,而我的手机系统是Android 9.0!!关于Android 9.0 关于http明文设置源码解析相关可以查看源码分析 Android 9.0 http请求适配原理。之前公司的测试曾提出无法在Android 7.0系统上使用Charles抓包,后通过官方资料网络安全配置发现在Android7.0系统上默认已经不再信任用户证书,按照上述文章配置即可,但是该配置只可以在测试环境下使用,生产环境不可以使用。参考配置构建变体配置两份文件即可。兜兜转转感觉早就应该想到是http问题才是,却迟钝了!!!之前公司使用MediaPlayer播放的也是https的地址。
  在实现过程中,发现如果在使用stop方法停止音频播放时,此时断网,调用异步准备方法可以重用刚刚缓冲好的音频内容,如果在stop后每次都重新设置新的数据源地址则都会重新从网络获取新的音频数据。在现有实现中,在每次播放器回调onError方法时都重新创建一个新的MediaPlayer,虽然根据官网文章得知可以reset,但是毕竟还未经测试,出错情况也不好测试,不清楚实际情况如何,后面再做测试讨论。
  另外附上整理的MediaPlayer管理类源码给需要的小伙伴,如果发现使用中有问题,可以留言修改:

package com.khronos.components.media;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Handler;
import android.text.TextUtils;

import com.orhanobut.logger.Logger;

import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 只支持音频播放
 * 结合MediaPlayer生命周期
 */
public class MediaPlayerManager {

    public static final String TAG = "MediaPlayerManager";
    
    private static final int STATE_UNINITIALIZED = 0;
    private static final int STATE_IDLE = 1;
    private static final int STATE_INITIALIZED = 2;
    private static final int STATE_PREPARING = 3;
    private static final int STATE_PREPARED = 4;
    private static final int STATE_STARTED = 5;
    private static final int STATE_PAUSED = 6;
    private static final int STATE_COMPLETED = 7;
    private static final int STATE_STOPED = 8;
    private static final int STATE_ERROR = 9;
    private static final int STATE_END = 10;


    private static volatile MediaPlayerManager sMediaPlayerManager;

    private MediaPlayer mMediaPlayer;

    private volatile int mCurrentSate = STATE_UNINITIALIZED;

    private MediaPlayer.OnCompletionListener mCompletionListener;
    private MediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener;
    private MediaPlayer.OnSeekCompleteListener mSeekCompleteListener;
    private MediaPlayer.OnPreparedListener mPreparedListener;
    private MediaPlayer.OnErrorListener mOnErrorListener;
    private ScheduledExecutorService mScheduledExecutorService;
    private SendPlayProgress mSendPlayProgress = new SendPlayProgress();
    private OnMediaProgressUpdateListener mOnMediaProgressUpdateListener;
    private Handler mMainHandler = new Handler();
    private MainRunnable mMainRunnable = new MainRunnable();
    private String mPlayingUrl;

    /**
     * 初始化进度回调线程池
     */
    private void initProgressSchedule() {
        if (mOnMediaProgressUpdateListener == null) {
            return;
        }
        if (mScheduledExecutorService == null || mScheduledExecutorService.isShutdown()) {
            mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
            mScheduledExecutorService.scheduleAtFixedRate(mSendPlayProgress, 0, 2, TimeUnit.MILLISECONDS);
        }
    }

    /**
     * 销毁进度回调线程池
     */
    private void destroyProgressSchedule() {
        if (mScheduledExecutorService != null && !mScheduledExecutorService.isShutdown()) {
            mScheduledExecutorService.shutdownNow();
        }
    }

    public void setOnMediaProgressUpdateListener(OnMediaProgressUpdateListener onMediaProgressUpdateListener) {
        mOnMediaProgressUpdateListener = onMediaProgressUpdateListener;
    }

    public void setOnErrorListener(MediaPlayer.OnErrorListener onErrorListener) {
        mOnErrorListener = onErrorListener;
    }

    /**
     * 进度更新回调
     */
    interface OnMediaProgressUpdateListener {
        /**
         * 进度更新
         *
         * @param progress 进度
         */
        void onProgress(int progress);
    }

    /**
     * 播放器单例
     *
     * @return 播放器
     */
    public static MediaPlayerManager getInstance() {
        if (sMediaPlayerManager == null) {
            synchronized (MediaPlayerManager.class) {
                if (sMediaPlayerManager == null) {
                    sMediaPlayerManager = new MediaPlayerManager();
                }
            }
        }
        return sMediaPlayerManager;
    }

    private MediaPlayerManager() {
        init();
    }

    /**
     * 初始化播放器
     */
    private synchronized void init() {
        release();
        mMediaPlayer = new MediaPlayer();
        mCurrentSate = STATE_IDLE;
        mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListenerInternal);
        mMediaPlayer.setOnCompletionListener(mOnCompletionListenerInternal);
        mMediaPlayer.setOnErrorListener(mOnErrorListenerInternal);
        mMediaPlayer.setOnInfoListener(mOnInfoListenerInternal);
        mMediaPlayer.setOnPreparedListener(mOnPreparedListenerInternal);
        mMediaPlayer.setOnSeekCompleteListener(mOnSeekCompleteListenerInternal);
        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    }

    /**
     * 每次播放音频都会创建一个新的播放器,并且释放上一个播放器
     *
     * @param url 地址 支持本地地址或者网络地址
     */
    public synchronized void playOnCreate(String url) {
        if (mCurrentSate == STATE_PAUSED) {
            start();
            return;
        }
        init();
        setDataSource(url);
        initProgressSchedule();
    }

    /**
     * 每次使用同一个播放器播放音频
     *
     * @param url 地址 支持本地地址或者网络地址
     */
    public synchronized void playOnOne(String url) {
        if (TextUtils.isEmpty(url)) {
            return;
        }
        //已经结束或者已经有正在播放的
        if (mCurrentSate == STATE_STARTED) {
            return;
        }
        //未创建MediaPlayer对象或者已经释放了
        if (mCurrentSate == STATE_UNINITIALIZED || mCurrentSate == STATE_END) {
            init();
        }
        //手动停止的情况下
        if (mCurrentSate == STATE_STOPED) {
            //如果此时断网然后重新播放同一个地址
            if (TextUtils.equals(mPlayingUrl, url)) {
                //使用异步准备方法可以继续播放,否则同个设置数据源的方式无法使用本地缓冲好的音频内容继续播放
                mMediaPlayer.prepareAsync();
                mCurrentSate = STATE_PREPARING;
                return;
            }
            reset();
        }
        //暂停播放
        if (mCurrentSate == STATE_PAUSED) {
            if (TextUtils.equals(mPlayingUrl, url)) {
                start();
                return;
            }
            reset();
        }
        //设置数据源
        mPlayingUrl = url;
        setDataSource(url);
        initProgressSchedule();
    }

    private MediaPlayer.OnSeekCompleteListener mOnSeekCompleteListenerInternal = new MediaPlayer.OnSeekCompleteListener() {
        @Override
        public void onSeekComplete(MediaPlayer mp) {
            Logger.t(TAG).e("onSeekComplete:mp = " + mp);
            if (mSeekCompleteListener != null) {
                mSeekCompleteListener.onSeekComplete(mp);
            }
        }
    };

    private MediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListenerInternal = new MediaPlayer.OnBufferingUpdateListener() {
        @Override
        public void onBufferingUpdate(MediaPlayer mp, int percent) {
            if (mOnBufferingUpdateListener != null) {
                mOnBufferingUpdateListener.onBufferingUpdate(mp, percent);
            }
            Logger.t(TAG).i("onBufferingUpdate:mp = " + mp + ", percent = " + percent);
        }
    };

    private MediaPlayer.OnCompletionListener mOnCompletionListenerInternal = new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) {
            mCurrentSate = STATE_COMPLETED;
            stop();
            Logger.t(TAG).i("onCompletion:mp = " + mp);
            if (mCompletionListener != null) {
                mCompletionListener.onCompletion(mp);
            }
        }
    };

    /**
     * 返回true,在出错的情况下将不会在继续调用{@link MediaPlayer.OnCompletionListener#onCompletion(MediaPlayer)}
     */
    private MediaPlayer.OnErrorListener mOnErrorListenerInternal = new MediaPlayer.OnErrorListener() {
        @Override
        public boolean onError(MediaPlayer mp, int what, int extra) {
            mCurrentSate = STATE_ERROR;
            getInstance().release();
            Logger.t(TAG).e("onError:mp = " + mp + ", what = " + what + ", extra = " + extra);
            if (mOnErrorListener != null) {
                mOnErrorListener.onError(mp, what, extra);
            }
            return true;
        }
    };

    private MediaPlayer.OnInfoListener mOnInfoListenerInternal = new MediaPlayer.OnInfoListener() {
        @Override
        public boolean onInfo(MediaPlayer mp, int what, int extra) {
            Logger.t(TAG).i("onInfo:mp = " + mp + ", what = " + what + ", extra = " + extra);
            return false;
        }
    };

    private MediaPlayer.OnPreparedListener mOnPreparedListenerInternal = new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mp) {
            mCurrentSate = STATE_PREPARED;
            getInstance().start();
            Logger.t(TAG).i("onPrepared:mp = " + mp);
            if (mPreparedListener != null) {
                mPreparedListener.onPrepared(mp);
            }
        }
    };

    /**
     * 播放器实例
     */
    public synchronized void release() {
        if (mMediaPlayer != null) {
            mMediaPlayer.release();
            mCurrentSate = STATE_END;
            mMediaPlayer = null;
        }
        mPlayingUrl = "";
        destroyProgressSchedule();
    }

    /**
     * 设置完成回调
     *
     * @param listener 完成回调
     */
    public void setCompleteListener(MediaPlayer.OnCompletionListener listener) {
        mCompletionListener = listener;
    }

    /**
     * 设置缓存回调
     *
     * @param listener 缓存回调
     */
    public void setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener) {
        mOnBufferingUpdateListener = listener;
    }

    /**
     * 设置滑动条
     *
     * @param listener 滑条监听
     */
    public void setSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener) {
        mSeekCompleteListener = listener;
    }

    /**
     * 设置播放准备完成
     *
     * @param preparedListener 播放准备完成
     */
    public void setPreparedListener(MediaPlayer.OnPreparedListener preparedListener) {
        this.mPreparedListener = preparedListener;
    }

    /**
     * 为了重用一个处于Error状态的MediaPlayer对象,可以调用reset()方法来把这个对象恢复成Idle状态。
     */
    private synchronized void reset() {
        try {
            destroyProgressSchedule();
            mMediaPlayer.reset();
            mCurrentSate = STATE_IDLE;
        } catch (Exception e) {
            Logger.t(TAG).e("reset:= " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 设置数据源,可以说本地文件路径,也可以是在线音频流url;
     *
     * @param url 地址
     */
    private synchronized void setDataSource(String url) {
        try {
            if (mCurrentSate != STATE_IDLE) {
                return;
            }
            mMediaPlayer.setDataSource(url);
//            Class<MediaPlayer> clazz = MediaPlayer.class;
//            Method method = clazz.getDeclaredMethod("setDataSource", String.class, Map.class);
//            method.invoke(mMediaPlayer, path, new HashMap<String, String>());
            mCurrentSate = STATE_INITIALIZED;
            mMediaPlayer.prepareAsync();
            mCurrentSate = STATE_PREPARING;
        } catch (IllegalStateException e) {
            e.printStackTrace();
            Logger.t(TAG).e("setDataSource:= " + e.getMessage());
        } catch (IOException e) {
            Logger.t(TAG).e("setDataSource:= " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 异步准备方法只有在 {@link #STATE_INITIALIZED} {@link #STATE_STOPED}状态下可以调用
     *
     * @return true 可以调用 false 不可以调用
     */
    private boolean isCanPrepareAsync() {
        return mCurrentSate == STATE_INITIALIZED || mCurrentSate == STATE_STOPED;
    }

    /**
     * 如果之前pasue的话,从pasue处播放;否则,从bengin处播放
     *
     * @return 开始
     */
    public synchronized boolean start() {
        try {
            if (!isCanStart()) {
                return false;
            }
            if (null != mMediaPlayer) {
                mMediaPlayer.start();
            }
            mCurrentSate = STATE_STARTED;
            return true;
        } catch (IllegalStateException e) {
            Logger.t(TAG).e("start ex " + e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            Logger.t(TAG).e("start ex " + e.getMessage());
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 启动方法只有在
     * {@link #STATE_PREPARED}
     * {@link #STATE_PAUSED}
     * {@link #STATE_PAUSED}
     * 状态下可以调用
     *
     * @return true 可以调用 false 不可以调用
     */
    private boolean isCanStart() {
        return mCurrentSate == STATE_PREPARED || mCurrentSate == STATE_PAUSED || mCurrentSate == STATE_COMPLETED;
    }

    /**
     * 暂停方法只有在 {@link #STATE_STARTED} 状态下可以调用
     *
     * @return true 可以调用 false 不可以调用
     */
    private boolean isCanPause() {
        return mCurrentSate == STATE_STARTED;
    }

    /**
     * 暂停
     */
    public synchronized void pause() {
        if (!isCanPause()) {
            return;
        }
        mMediaPlayer.pause();
        mCurrentSate = STATE_PAUSED;
    }

    /**
     * 检查相关方法在生命周期内是否可以调用
     *
     * @return true 可以调用 false 不可以调用
     */
    private boolean isCanCall() {
        if (mCurrentSate == STATE_IDLE || mCurrentSate == STATE_END || mCurrentSate == STATE_PREPARING) {
            return false;
        }
        return true;
    }

    /**
     * 停止
     *
     * @return 停止
     */
    public synchronized boolean stop() {
        try {
            if (!isCanStop()) {
                return false;
            }
            mMediaPlayer.stop();
            mCurrentSate = STATE_STOPED;
            return true;
        } catch (IllegalStateException e) {
            Logger.t(TAG).e("stop ex " + e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            Logger.t(TAG).e("stop ex " + e.getMessage());
            e.printStackTrace();
        }
        destroyProgressSchedule();
        return false;
    }

    /**
     * 暂停方法只需要在
     * {@link #STATE_PAUSED}
     * {@link #STATE_STARTED}
     * {@link #STATE_PREPARED}状态下调用
     * {@link #STATE_COMPLETED}状态下调用
     *
     * @return true 可以调用 false 不可以调用
     */
    private boolean isCanStop() {
        return mCurrentSate == STATE_PAUSED || mCurrentSate == STATE_STARTED || mCurrentSate == STATE_PREPARED
                || mCurrentSate == STATE_COMPLETED;
    }

    /**
     * 获取当前状态
     *
     * @return 当前状态
     */
    public synchronized int getCurrentState() {
        return mCurrentSate;
    }

    /**
     * 是否暂停状态
     *
     * @return 是否暂停
     */
    public synchronized boolean isPauseState() {
        return mCurrentSate == STATE_PAUSED;
    }

    /**
     * 是否已经开启的状态
     *
     * @return 是否开启
     */
    public synchronized boolean isStartedState() {
        return mCurrentSate == STATE_PREPARED;
    }

    /**
     * 设置左右声音音量
     *
     * @param left  左音道
     * @param right 右音道
     */
    public synchronized void setVolume(float left, float right) {
        if (!isCanSet()) {
            return;
        }
        mMediaPlayer.setVolume(left, right);
    }

    /**
     * 获取当前进度,单位是毫秒
     *
     * @return 进度
     */
    private synchronized int getCurrentPosition() {
        return mMediaPlayer.getCurrentPosition();
    }

    /**
     * 获取时长,单位是毫秒;如果不可用,则返回-1;
     *
     * @return 时长
     */
    private synchronized int getDuration() {
        return mMediaPlayer.getDuration();
    }

    /**
     * 是否循环
     *
     * @param looping 是否循环
     */
    public synchronized void setLooping(boolean looping) {
        if (!isCanSet()) {
            return;
        }
        mMediaPlayer.setLooping(looping);
    }

    /**
     * 只有在 {@link #STATE_PREPARED}状态下才可以修改播放器属性
     *
     * @return true 可以调用 false 不可以调用
     */
    private synchronized boolean isCanSet() {
        return mCurrentSate == STATE_PREPARED;
    }


    /**
     * 是否循环
     *
     * @return 是否循环
     */
    public synchronized boolean isLooping() {
        return mMediaPlayer.isLooping();
    }

    /**
     * isPlaying()方法可以被调用来测试某个MediaPlayer对象是否在Started状态。
     *
     * @return 是否在播放
     */
    public synchronized boolean isPlaying() {
        if (mCurrentSate == STATE_END) {
            return false;
        }
        try {
            return mMediaPlayer.isPlaying();
        } catch (Exception e) {
            Logger.t(TAG).e("isPlaying ex " + e.getMessage());
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 拉动进度
     *
     * @param position 进度
     */
    public synchronized void seekTo(int position) {
        if (!isCanSeekTo()) {
            return;
        }
        mMediaPlayer.seekTo(position);
    }

    /**
     * 该方法只需要在
     * {@link #STATE_PAUSED}
     * {@link #STATE_COMPLETED}
     * {@link #STATE_PREPARED}状态下调用
     *
     * @return true 可以调用 false 不可以调用
     */
    private boolean isCanSeekTo() {
        return mCurrentSate == STATE_PREPARED
                || mCurrentSate == STATE_PAUSED
                || mCurrentSate == STATE_COMPLETED
                || mCurrentSate == STATE_STARTED;
    }

    class MainRunnable implements Runnable {
        public int progress;

        @Override
        public void run() {
            if (mOnMediaProgressUpdateListener != null) {
                mOnMediaProgressUpdateListener.onProgress(progress);
            }
        }
    }

    private class SendPlayProgress implements Runnable {
        @Override
        public void run() {
            if (!isCanCall()) {
                return;
            }
            if (mMediaPlayer == null || !isPlaying()) {
                return;
            }
            mMainRunnable.progress = (int) ((getCurrentPosition() * 1.0f / getDuration() * 1.0f) * 100 + 0.9f);
            mMainHandler.post(mMainRunnable);

        }
    }

}

  如果需要顺序播放多个音频,并且希望无缝切换则可以考虑在当前播放器播放音频的时候去创建一个播放器预加载下一条音频,在使用网易云音乐的测试时,音频切换间隔在10几毫秒的样子。如果不做预加载那么在顺序加载播放的情况下,音频切换间隔则在600+毫秒左右。如果音频地址有问题或者因为网络问题加载过慢,则可以考虑加入超时机制加以判断。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352