CarGuo/GSYVideoPlayer · error · IOException
无法创建目录: ${parentDir.getAbsolutePath()}
Error message
无法创建目录: ${parentDir.getAbsolutePath()} What it means
Media3CacheExportUtils.prepareExportFile tries to guarantee the parent directory of the export target file exists before writing. If parentDir.mkdirs() fails AND the directory still does not exist afterwards (typical race or missing storage permission), it throws IOException with the directory path. This is a pre-flight check to avoid a later FileNotFoundException from the export write.
Source
Thrown at gsyVideoPlayer-exo_player2/src/main/java/tv/danmaku/ijk/media/exo2/Media3CacheExportUtils.java:76
// 如果未指定路径,默认使用应用私有下载目录
// 优点:不需要申请存储权限,Android 10+ 也能直接写,卸载应用自动清除
if (finalTargetFile == null) {
File dir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
if (dir == null) {
dir = context.getFilesDir(); // 极端情况兜底
}
// 根据时间戳生成文件名,避免冲突
String fileName = "video_export_" + System.currentTimeMillis() + ".mp4";
finalTargetFile = new File(dir, fileName);
}
// 关键修复:确保父文件夹存在,否则会报 FileNotFoundException
File parentDir = finalTargetFile.getParentFile();
if (parentDir != null && !parentDir.exists()) {
boolean created = parentDir.mkdirs();
if (!created && !parentDir.exists()) {
throw new IOException("无法创建目录: " + parentDir.getAbsolutePath());
}
}
// --- 2. Cache 获取逻辑 (复用单例) ---
// 使用 GSYVideoPlayer 现有的单例 Cache
// 修复了 "Another SimpleCache instance uses the folder" 错误
Cache cache = ExoSourceManager.acquireCacheSingleInstance(context, null);
if (cache == null) {
throw new IllegalStateException("ExoPlayer Cache 未初始化,请先播放视频");
}
try {
// --- 3. 导出核心逻辑 ---
DataSpec dataSpec = new DataSpec.Builder()
.setUri(Uri.parse(videoUrl))
.setFlags(DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION)
.build();
// 使用 DefaultDataSource,如果缓存缺了一点点,它会自动联网补齐,而不是崩溃View on GitHub (pinned to e5d74d3aa9)
Solutions
- Use context.getExternalFilesDir(null) or context.getFilesDir() as the export directory (app-scoped, no permission needed)
- On Android 10+ export via MediaStore.Downloads/Movies instead of direct File paths
- Verify storage permissions (WRITE_EXTERNAL_STORAGE for API <= 29) before calling export
- Check Environment.getExternalStorageState().equals(MEDIA_MOUNTED) before targeting external storage
Example fix
// before File dir = new File(Environment.getExternalStorageDirectory(), "exports"); File target = new File(dir, "video_export_" + ts + ".mp4"); // after - app-scoped dir, always creatable File dir = context.getExternalFilesDir(null); if (dir == null) dir = context.getFilesDir(); File target = new File(dir, "video_export_" + ts + ".mp4");
Defensive patterns
Strategy: validation
Validate before calling
File parent = targetFile.getParentFile();
if (parent == null || (!parent.exists() && !parent.mkdirs() && !parent.exists())) {
// pick a guaranteed-writable dir instead
targetFile = new File(context.getFilesDir(), targetFile.getName());
} Try / catch
try {
export(videoUrl, targetFile);
} catch (IOException e) {
// dir creation failed: surface actionable message, fall back to app dir
export(videoUrl, new File(context.getFilesDir(), targetFile.getName()));
} Prevention
- Prefer app-scoped dirs (getExternalFilesDir/getFilesDir) over public storage
- On Android 10+ use MediaStore for shared-storage exports
- Check external storage mount state before targeting it
When it happens
Trigger: Calling the export API with a target dir on external storage (Environment.getExternalStorageDirectory or a public Movies/Download path) when the dir does not exist and either WRITE_EXTERNAL_STORAGE is missing, the volume is unmounted, or another thread deletes the directory between mkdirs() and exists().
Common situations: Android 10+ scoped storage: writing to shared storage paths without MANAGE_EXTERNAL_STORAGE or MediaStore; target dir on a removable SD card that was ejected; directory name containing characters the filesystem rejects; running export in a process without storage permissions.
Related errors
- Error reading source ${errorsCount} times
- File %s is not directory!
- ExoPlayer Cache 未初始化,请先播放视频
- Reading source ${sourceInfo.url} is interrupted
- Error using file ${file} as disc cache
AI-assisted analysis of CarGuo/GSYVideoPlayer@e5d74d3aa9 (2026-08-14).
Data as JSON: /api/errors/815ea96cbc1d2946.
Report an issue: GitHub.