首先这个问题无法修复。
使用MediaPlayer的时候播放网络音频出现了以下错误,记录如下:
原代码:
/**
* 播放音频文件
*/
private void playVoice(String voicePath){
MediaPlayer player = MediaPlayer.create(context, Uri.parse(voicePath));
player.start();
}
错误日志:
W/MediaPlayer: Couldn't open https://dq-oss.oss-cn-beijing.aliyuncs.com/...
java.io.FileNotFoundException: No content provider: https://dq-oss.oss-cn-beijing.aliyuncs.com/audio_recordrecord_20200414_14_32_58.mp3
at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1688)
at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1518)
at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1435)
at android.media.MediaPlayer.attemptDataSource(MediaPlayer.java:1154)
at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1112)
at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1019)
at android.media.MediaPlayer.create(MediaPlayer.java:926)
at android.media.MediaPlayer.create(MediaPlayer.java:903)
at android.media.MediaPlayer.create(MediaPlayer.java:882)
at com.example.qcsdk.adapter.ChatP2PAdapter.playVoice(ChatP2PAdapter.java:255)
at com.example.qcsdk.adapter.ChatP2PAdapter.access$000(ChatP2PAdapter.java:40)
at com.example.qcsdk.adapter.ChatP2PAdapter$2.onClick(ChatP2PAdapter.java:222)
at android.view.View.performClick(View.java:7161)
at android.view.View.performClickInternal(View.java:7138)
at android.view.View.access$3500(View.java:811)
at android.view.View$PerformClick.run(View.java:27419)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7520)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
这个错误是在小米上,虽然报错了但是还是会有声音,不过为了防止出现其他问题,还是进行了修复,修改后的代码如下:
/**
* 播放音频文件
*/
private void playVoice(String voicePath){
// MediaPlayer player = MediaPlayer.create(context, Uri.parse(voicePath));
MediaPlayer player = new MediaPlayer();
try {
player.setDataSource(voicePath);
}catch (IOException e){
e.printStackTrace();
}
player.prepareAsync();
player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.start();
}
});
}
这个问题的原因是因为 Uri.parse(voicePath)
把网络路径转换成了本地路径,这时候本地没有这个文件才出现这个问题,比如上述的https://dq-oss.oss-cn-beijing.aliyuncs.com/audio_recordrecord_20200414_14_32_58.mp3使用 Uri.parse(voicePath)
转换为路径后结果为/audio_recordrecord_20200414_14_32_58.mp3
,这样当然不能播放,不过有的手机即使报错了依然可以播放,还不知道是为什么。不过按照Android源码来看的话,是绝对会报错的。下面解释源码的问题。
问题主要出在MediaPlayer上面,当使用Mediaplayer执行setDataSource(@NonNull Context context, @NonNull Uri uri)
函数时候,最终会执行到
setDataSource(@NonNull Context context, @NonNull Uri uri,
@Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies)
里面,这里我们看下这个函数的代码:
MediaPlayer.java
/**
* Sets the data source as a content Uri.
*
* To provide cookies for the subsequent HTTP requests, you can install your own default cookie
* handler and use other variants of setDataSource APIs instead. Alternatively, you can use
* this API to pass the cookies as a list of HttpCookie. If the app has not installed
* a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
* the provided cookies. If the app has installed its own handler already, this API requires the
* handler to be of CookieManager type such that the API can update the manager’s CookieStore.
*
* Note that the cross domain redirection is allowed by default,
* but that can be changed with key/value pairs through the headers parameter with
* "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
* disallow or allow cross domain redirection.
*
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @param headers the headers to be sent together with the request for the data
* The headers must not include cookies. Instead, use the cookies param.
* @param cookies the cookies to be sent together with the request
* @throws IllegalArgumentException if cookies are provided and the installed handler is not
* a CookieManager
* @throws IllegalStateException if it is called in an invalid state
* @throws NullPointerException if context or uri is null
* @throws IOException if uri has a file scheme and an I/O error occurs
*/
public void setDataSource(@NonNull Context context, @NonNull Uri uri,
@Nullable Map headers, @Nullable List cookies)
throws IOException {
if (context == null) {
throw new NullPointerException("context param can not be null.");
}
if (uri == null) {
throw new NullPointerException("uri param can not be null.");
}
if (cookies != null) {
CookieHandler cookieHandler = CookieHandler.getDefault();
if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
throw new IllegalArgumentException("The cookie handler has to be of CookieManager "
+ "type when cookies are provided.");
}
}
// The context and URI usually belong to the calling user. Get a resolver for that user
// and strip out the userId from the URI if present.
final ContentResolver resolver = context.getContentResolver();
final String scheme = uri.getScheme();
final String authority = ContentProvider.getAuthorityWithoutUserId(uri.getAuthority());
if (ContentResolver.SCHEME_FILE.equals(scheme)) {
setDataSource(uri.getPath());
return;
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
&& Settings.AUTHORITY.equals(authority)) {
// Try cached ringtone first since the actual provider may not be
// encryption aware, or it may be stored on CE media storage
final int type = RingtoneManager.getDefaultType(uri);
final Uri cacheUri = RingtoneManager.getCacheForType(type, context.getUserId());
final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
if (attemptDataSource(resolver, cacheUri)) {
return;
} else if (attemptDataSource(resolver, actualUri)) {
return;
} else {
setDataSource(uri.toString(), headers, cookies);
}
} else {
// Try requested Uri locally first, or fallback to media server
if (attemptDataSource(resolver, uri)) {
return;
} else {
setDataSource(uri.toString(), headers, cookies);
}
}
}
因为路径是网络链接,以http
或者https
开头,所以会执行到最后的else代码块中,然后会执行到 attemptDataSource(ContentResolver resolver, Uri uri)
函数,下面看下这个函数:
MediaPlayer.java
private boolean attemptDataSource(ContentResolver resolver, Uri uri) {
try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) {
setDataSource(afd);
return true;
} catch (NullPointerException | SecurityException | IOException ex) {
Log.w(TAG, "Couldn't open " + (uri == null ? "null uri" : uri.toSafeString()), ex);
return false;
}
}
可以看到第一行代码是用来判断Asset的资源文件,这时候就会出现报错,因为我们的资源是网络资源…
暂时不知道怎么解决这个事情,等Android更新吧…
想验证这个问题的话把try里面的代码
AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")
复制出来测试下就行了