{"record":{"id":"0b3ebc870ec6c0f0","repo":"CarGuo/GSYVideoPlayer","slug":"exoplayer-cache","errorCode":null,"errorMessage":"ExoPlayer Cache 未初始化，请先播放视频","messagePattern":"ExoPlayer Cache 未初始化，请先播放视频","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"gsyVideoPlayer-exo_player2/src/main/java/tv/danmaku/ijk/media/exo2/Media3CacheExportUtils.java","lineNumber":85,"sourceCode":"                    String fileName = \"video_export_\" + System.currentTimeMillis() + \".mp4\";\n                    finalTargetFile = new File(dir, fileName);\n                }\n\n                // 关键修复：确保父文件夹存在，否则会报 FileNotFoundException\n                File parentDir = finalTargetFile.getParentFile();\n                if (parentDir != null && !parentDir.exists()) {\n                    boolean created = parentDir.mkdirs();\n                    if (!created && !parentDir.exists()) {\n                        throw new IOException(\"无法创建目录: \" + parentDir.getAbsolutePath());\n                    }\n                }\n\n                // --- 2. Cache 获取逻辑 (复用单例) ---\n                // 使用 GSYVideoPlayer 现有的单例 Cache\n                // 修复了 \"Another SimpleCache instance uses the folder\" 错误\n                Cache cache = ExoSourceManager.acquireCacheSingleInstance(context, null);\n                if (cache == null) {\n                    throw new IllegalStateException(\"ExoPlayer Cache 未初始化，请先播放视频\");\n                }\n                try {\n                    // --- 3. 导出核心逻辑 ---\n                    DataSpec dataSpec = new DataSpec.Builder()\n                        .setUri(Uri.parse(videoUrl))\n                        .setFlags(DataSpec.FLAG_ALLOW_CACHE_FRAGMENTATION)\n                        .build();\n\n                    // 使用 DefaultDataSource，如果缓存缺了一点点，它会自动联网补齐，而不是崩溃\n                    CacheDataSource dataSource = new CacheDataSource(\n                        cache,\n                        new DefaultDataSource(context, true), // <--- 修正：传入 Context，支持 HTTP/HTTPS 和 File\n                        CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR\n                    );\n                    try {\n                        long contentLength = ContentMetadata.getContentLength(cache.getContentMetadata(videoUrl));\n                        if (contentLength <= 0) {\n                            contentLength = dataSource.open(dataSpec);","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/CarGuo/GSYVideoPlayer/blob/e5d74d3aa9d7fb1393a879e33ee380f8f41354f1/gsyVideoPlayer-exo_player2/src/main/java/tv/danmaku/ijk/media/exo2/Media3CacheExportUtils.java#L67-L103","documentation":"Media3CacheExportUtils needs an already-initialized ExoPlayer SimpleCache to read cached segments from; it calls ExoSourceManager.acquireCacheSingleInstance(context, null) which returns null when caching was never enabled or the folder is locked with fallback (fallbackWhenLocked path returned null). Throwing IllegalStateException tells the caller the export precondition (a played, cached video) is not met.","triggerScenarios":"Calling exportCachedVideo before any GSYVideoPlayer playback created the cache, when ExoSourceManager had zero cache holders (sCacheHolderMap empty), or when the cache folder was locked and the fallback overload returned null instead of a Cache.","commonSituations":"Export feature invoked from a screen where video never played; app was restarted and cache instance is gone but UI still offers 'export'; cache disabled in GSYVideoType/PlayerFactory config; multi-process: export runs in a process where the cache was never created.","solutions":["Start playback (which initializes the ExoPlayer cache) before invoking export, and only enable the export UI after onPrepared","Pre-create the cache at app start via ExoSourceManager.acquireCacheSingleInstance(context, cacheDir, true) so export always finds it","Handle null cache gracefully: show 'video not cached yet' message instead of crashing","Ensure export runs in the same process that owns the player cache"],"exampleFix":"// before\nCache cache = ExoSourceManager.acquireCacheSingleInstance(context, null);\nif (cache == null) throw new IllegalStateException(\"ExoPlayer Cache 未初始化，请先播放视频\");\n\n// after - ensure cache exists up front, guard UI on result\nCache cache = ExoSourceManager.acquireCacheSingleInstance(context, null);\nif (cache == null) {\n    callback.onExportUnavailable(\"Play the video first so it can be cached\");\n    return;\n}","handlingStrategy":"validation","validationCode":"Cache cache = ExoSourceManager.acquireCacheSingleInstance(context, null);\nif (cache == null) {\n    // don't call export; initialize cache by starting playback or pre-create it\n    cache = ExoSourceManager.acquireCacheSingleInstance(context, new File(context.getCacheDir(), \"exo\"), true);\n}","typeGuard":null,"tryCatchPattern":"try {\n    exportCachedVideo(context, url, target);\n} catch (IllegalStateException e) {\n    if (\"ExoPlayer Cache 未初始化，请先播放视频\".equals(e.getMessage())) {\n        showHint(\"Play the video first, then export\");\n    } else throw e;\n}","preventionTips":["Enable the export UI only after playback has initialized the cache","Pre-create the cache at app start in the same process","Keep player and export in one process"],"tags":["exoplayer","cache","precondition","export","android"],"backgroundTag":null,"analyzedSha":"e5d74d3aa9d7fb1393a879e33ee380f8f41354f1","analyzedAt":"2026-08-14T11:56:11.997Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}