使用ExoPlayer流式播放音频时遇SSLHandshakeException问题求助
我之前碰到过一模一样的问题,本质就是你的流媒体服务器用了Android系统默认不信任的SSL证书——要么是自签名证书,要么是用了自定义CA颁发的证书,导致ExoPlayer在建立SSL连接时通不过证书验证。下面给你两种实用的解决办法,根据你的场景选:
方法一:将证书打包到应用中(推荐生产环境使用)
这个方法是把服务器的证书添加到应用的信任仓库里,让ExoPlayer信任这个证书,安全合规。
获取并放置证书文件
先从你的流媒体服务器导出X.509格式的证书(通常是.cer或.pem后缀),把它放到你的Android项目的src/main/assets目录下(如果没有assets文件夹就新建一个)。假设证书文件名是stream_server.cer。编写工具类加载证书并构建SSLContext
创建一个工具类来处理证书加载和SSL上下文的初始化:import android.content.Context; import java.io.InputStream; import java.security.KeyStore; import java.security.cert.CertificateFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; public class SSLHelper { public static SSLContext getTrustedSSLContext(Context context) throws Exception { // 加载assets中的证书 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); InputStream certStream = context.getAssets().open("stream_server.cer"); java.security.cert.Certificate serverCert = certFactory.generateCertificate(certStream); certStream.close(); // 创建KeyStore并添加证书 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null, null); keyStore.setCertificateEntry("trusted_server_cert", serverCert); // 初始化信任管理器工厂 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() ); trustManagerFactory.init(keyStore); // 构建并返回SSLContext SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustManagerFactory.getTrustManagers(), null); return sslContext; } }为ExoPlayer配置自定义数据源
推荐使用ExoPlayer的OkHttp扩展来配置自定义SSL(比默认的DefaultHttpDataSource更灵活),先确保你的build.gradle里添加了依赖:implementation "com.google.android.exoplayer:extension-okhttp:${exoplayer_version}"然后创建带自定义SSL的OkHttpClient,并配置给ExoPlayer:
try { SSLContext sslContext = SSLHelper.getTrustedSSLContext(getApplicationContext()); // 这里需要获取TrustManagerFactory实例,可在工具类中返回或调整代码逻辑 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init((KeyStore) null); // 这里对应工具类中的KeyStore初始化逻辑,可优化为工具类返回 OkHttpClient okHttpClient = new OkHttpClient.Builder() .sslSocketFactory( sslContext.getSocketFactory(), (X509TrustManager) trustManagerFactory.getTrustManagers()[0] ) .build(); // 创建自定义数据源工厂 HttpDataSource.Factory dataSourceFactory = new OkHttpDataSource.Factory(okHttpClient); // 初始化ExoPlayer时使用这个数据源工厂 ExoPlayer player = new ExoPlayer.Builder(this) .setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory)) .build(); } catch (Exception e) { e.printStackTrace(); // 处理证书加载或SSL初始化异常 }
方法二:临时绕过证书验证(仅测试环境可用)
如果是测试阶段不想折腾证书,可以临时用这个方法跳过证书验证,但绝对不能在生产环境使用——这会完全暴露你的应用在SSL安全风险下:
import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.security.cert.X509Certificate; import java.security.SecureRandom; // 自定义信任所有证书的TrustManager private static final TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) {} @Override public void checkServerTrusted(X509Certificate[] chain, String authType) {} @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } }; // 构建信任所有证书的SSLContext private SSLContext getUnsafeSSLContext() throws Exception { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, TRUST_ALL_CERTS, new SecureRandom()); return sslContext; } // 配置到ExoPlayer的OkHttp数据源 try { OkHttpClient okHttpClient = new OkHttpClient.Builder() .sslSocketFactory( getUnsafeSSLContext().getSocketFactory(), (X509TrustManager) TRUST_ALL_CERTS[0] ) .hostnameVerifier((hostname, session) -> true) // 同时跳过主机名验证 .build(); HttpDataSource.Factory dataSourceFactory = new OkHttpDataSource.Factory(okHttpClient); ExoPlayer player = new ExoPlayer.Builder(this) .setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory)) .build(); } catch (Exception e) { e.printStackTrace(); }
注意事项
- 确保你的证书文件是正确的X.509格式,如果导出的是
.pem文件,也可以直接用,代码里的CertificateFactory.getInstance("X.509")能处理。 - 不要忘记添加网络权限到
AndroidManifest.xml:<uses-permission android:name="android.permission.INTERNET" /> - 生产环境一定要用方法一,方法二只适合本地测试或内部调试。
内容的提问来源于stack exchange,提问作者Kartheek
相关产品推荐
相关产品推荐

