{"record":{"id":"466f26eaf1466149","repo":"CarGuo/GSYVideoPlayer","slug":"error-reading-source-errorscount-times","errorCode":null,"errorMessage":"Error reading source ${errorsCount} times","messagePattern":"Error reading source (.+?) times","errorType":"exception","errorClass":"ProxyCacheException","httpStatus":null,"severity":"error","filePath":"gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/ProxyCache.java","lineNumber":56,"sourceCode":"\n        while (!cache.isCompleted() && cache.available() < (offset + length) && !stopped) {\n            readSourceAsync();\n            waitForSourceData();\n            checkReadSourceErrorsCount();\n        }\n        int read = cache.read(buffer, offset, length);\n        if (cache.isCompleted() && percentsAvailable != 100) {\n            percentsAvailable = 100;\n            onCachePercentsAvailableChanged(100);\n        }\n        return read;\n    }\n\n    private void checkReadSourceErrorsCount() throws ProxyCacheException {\n        int errorsCount = readSourceErrorsCount.get();\n        if (errorsCount >= MAX_READ_SOURCE_ATTEMPTS) {\n            readSourceErrorsCount.set(0);\n            throw new ProxyCacheException(\"Error reading source \" + errorsCount + \" times\");\n        }\n    }\n\n    public void shutdown() {\n        synchronized (stopLock) {\n            try {\n                stopped = true;\n                if (sourceReaderThread != null) {\n                    sourceReaderThread.interrupt();\n                }\n                cache.close();\n            } catch (ProxyCacheException e) {\n                onError(e);\n            }\n        }\n    }\n\n    private synchronized void readSourceAsync() throws ProxyCacheException {","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/CarGuo/GSYVideoPlayer/blob/e5d74d3aa9d7fb1393a879e33ee380f8f41354f1/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/ProxyCache.java#L38-L74","documentation":"FileCache's constructor makes the parent directory, decides between the completed file (read-only 'r') or the .download temp file ('rw'), and opens a RandomAccessFile on it. Any IOException in that setup - unwritable dir, missing permissions, file locked by another process - becomes ProxyCacheException('Error using file <path> as disc cache').","triggerScenarios":"HttpProxyCacheServer starts caching a new url and creates FileCache for the .download temp file when: cache directory is on read-only/unmounted storage, disk is full, another process holds the file, or a non-existent parent path (Files.makeDir failed differently).","commonSituations":"Cache dir configured to external storage that got unmounted; Android scoped-storage restrictions applied to a shared cache path; disk-full devices; two proxy servers pointed at the same cache folder from different processes.","solutions":["Use context.getExternalCacheDir() or context.getCacheDir() as the proxy cache root (app-scoped)","Check usable space before playback on low-storage devices and clear LRU files via the configured DiskUsage","Ensure only one HttpProxyCacheServer instance per cache directory","Verify Environment.getExternalStorageState() when the cache dir is user-configurable"],"exampleFix":"// before\nnew HttpProxyCacheServer.Builder(context)\n    .cacheDirectory(new File(\"/sdcard/gsy_proxy_cache\"))\n    .build();\n\n// after - app-scoped cache dir\nnew HttpProxyCacheServer.Builder(context)\n    .cacheDirectory(new File(context.getExternalCacheDir(), \"gsy_proxy_cache\"))\n    .build();","handlingStrategy":"validation","validationCode":"File cacheDir = new File(context.getExternalCacheDir(), \"proxy\");\nString state = Environment.getExternalStorageState();\nboolean writable = Environment.MEDIA_MOUNTED.equals(state)\n        && cacheDir.canWrite();\nif (!writable) cacheDir = new File(context.getCacheDir(), \"proxy\");","typeGuard":null,"tryCatchPattern":"try { server = new Builder(ctx).cacheDirectory(dir).build(); }\ncatch (ProxyCacheException e) {\n    if (String.valueOf(e.getMessage()).contains(\"as disc cache\")) {\n        server = new Builder(ctx).cacheDirectory(new File(ctx.getCacheDir(), \"proxy\")).build();\n    } else throw e;\n}","preventionTips":["Use app-scoped cache dirs only","One proxy server per cache directory","Check free space before long caching sessions"],"tags":["io","disk","storage","proxy-cache","permissions"],"backgroundTag":null,"analyzedSha":"e5d74d3aa9d7fb1393a879e33ee380f8f41354f1","analyzedAt":"2026-08-14T11:56:11.997Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}