├── .nvmrc ├── .watchmanconfig ├── example ├── .watchmanconfig ├── jest.config.js ├── .bundle │ └── config ├── app.json ├── ios │ ├── File.swift │ ├── CacheVideoExample │ │ ├── Images.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── AppDelegate.h │ │ ├── main.m │ │ ├── AppDelegate.mm │ │ ├── Info.plist │ │ └── LaunchScreen.storyboard │ ├── CacheVideoExample-Bridging-Header.h │ ├── CacheVideoExample.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── .xcode.env │ ├── CacheVideoExampleTests │ │ ├── Info.plist │ │ └── CacheVideoExampleTests.m │ ├── Podfile │ └── CacheVideoExample.xcodeproj │ │ └── xcshareddata │ │ └── xcschemes │ │ └── CacheVideoExample.xcscheme ├── android │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── values │ │ │ │ │ │ ├── strings.xml │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ └── drawable │ │ │ │ │ │ └── rn_edit_text_material.xml │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── cachevideoexample │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ └── MainApplication.java │ │ │ ├── debug │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── cachevideoexample │ │ │ │ │ └── ReactNativeFlipper.java │ │ │ └── release │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── cachevideoexample │ │ │ │ └── ReactNativeFlipper.java │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ ├── build.gradle │ ├── gradle.properties │ └── gradlew.bat ├── index.js ├── Gemfile ├── react-native.config.js ├── babel.config.js ├── src │ ├── App.tsx │ ├── components │ │ ├── SingleVideo.tsx │ │ ├── VideoItem.tsx │ │ └── VideoList.tsx │ └── hooks │ │ └── useVideoInBackground.tsx ├── package.json ├── metro.config.js └── README.md ├── src ├── __tests__ │ └── index.test.tsx ├── Utils │ ├── index.ts │ ├── constants.ts │ └── __deprecated_func.txt ├── Libs │ ├── index.ts │ └── session.ts ├── Hooks │ ├── index.ts │ ├── useIsForeground.ts │ ├── useCache.ts │ └── useProxyCacheProvider.tsx ├── Provider │ ├── index.ts │ ├── MemoryCacheFreePolicy.ts │ ├── MemoryCacheProvider.ts │ └── MemoryCacheLFUPolicy.ts ├── index.tsx ├── NativeCacheVideoHttpProxy.ts ├── __mock__ │ └── react-native-blob-util.js ├── user-defined-guard │ └── index.ts └── types │ └── type.d.ts ├── .gitattributes ├── tsconfig.build.json ├── babel.config.js ├── android ├── src │ ├── main │ │ ├── AndroidManifestNew.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── cachevideo │ │ │ ├── CacheVideoHttpProxyPackage.java │ │ │ ├── CacheVideoHttpProxyModule.java │ │ │ └── httpServer │ │ │ └── Server.java │ ├── newarch │ │ └── CacheVideoHttpProxySpec.java │ └── oldarch │ │ └── CacheVideoHttpProxySpec.java ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew.bat └── build.gradle ├── .editorconfig ├── .yarnrc.yml ├── ios ├── CacheVideoHttpProxy.h ├── GCDWebServer │ ├── LICENSE │ └── GCDWebServer │ │ ├── Requests │ │ ├── GCDWebServerFileRequest.h │ │ ├── GCDWebServerURLEncodedFormRequest.h │ │ ├── GCDWebServerURLEncodedFormRequest.m │ │ ├── GCDWebServerDataRequest.h │ │ ├── GCDWebServerDataRequest.m │ │ ├── GCDWebServerFileRequest.m │ │ └── GCDWebServerMultiPartFormRequest.h │ │ ├── Responses │ │ ├── GCDWebServerStreamedResponse.m │ │ ├── GCDWebServerStreamedResponse.h │ │ ├── GCDWebServerErrorResponse.h │ │ ├── GCDWebServerDataResponse.h │ │ ├── GCDWebServerFileResponse.h │ │ └── GCDWebServerDataResponse.m │ │ └── Core │ │ ├── GCDWebServerFunctions.h │ │ └── GCDWebServerHTTPStatusCodes.h └── CacheVideoHttpProxy.mm ├── lefthook.yml ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── actions │ └── setup │ │ └── action.yml ├── pull_request_template.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md └── workflows │ └── ci.yml ├── turbo.json ├── tsconfig.json ├── scripts └── pod-install.cjs ├── .gitignore ├── LICENSE ├── react-native-cache-video.podspec ├── package.json ├── README.md └── CODE_OF_CONDUCT.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v18 2 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /src/__tests__/index.test.tsx: -------------------------------------------------------------------------------- 1 | it.todo('write a test'); 2 | -------------------------------------------------------------------------------- /example/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | }; 4 | -------------------------------------------------------------------------------- /src/Utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './util'; 2 | export * from './constants'; 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | # specific for windows script files 3 | *.bat text eol=crlf -------------------------------------------------------------------------------- /example/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig", 4 | "exclude": ["example"] 5 | } 6 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CacheVideoExample", 3 | "displayName": "CacheVideoExample" 4 | } 5 | -------------------------------------------------------------------------------- /example/ios/File.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // CacheVideoExample 4 | // 5 | 6 | import Foundation 7 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifestNew.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/Libs/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../Libs/session'; 2 | export * from '../Libs/fileSystem'; 3 | export * from '../Libs/httpProxy'; 4 | -------------------------------------------------------------------------------- /src/Hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useProxyCacheProvider'; 2 | export * from './useIsForeground'; 3 | export * from './useCache'; 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CacheVideoExample 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/app/debug.keystore -------------------------------------------------------------------------------- /example/ios/CacheVideoExample/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/ios/CacheVideoExample-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /example/ios/CacheVideoExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | CacheVideo_kotlinVersion=1.7.0 2 | CacheVideo_minSdkVersion=21 3 | CacheVideo_targetSdkVersion=31 4 | CacheVideo_compileSdkVersion=31 5 | CacheVideo_ndkversion=21.4.7075529 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './src/App'; 3 | import { name as appName } from './app.json'; 4 | 5 | AppRegistry.registerComponent(appName, () => App); 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nguyenvanphituoc/react-native-cache-video/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src/Provider/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MemoryCacheLFUPolicy'; 2 | export * from './MemoryCacheLFUSizePolicy'; 3 | export * from './MemoryCacheProvider'; 4 | export * from './PreCacheProvider'; 5 | export * from './MemoryCacheFreePolicy'; 6 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | 6 | gem 'cocoapods', '~> 1.13' 7 | gem 'activesupport', '>= 6.1.7.3', '< 7.1.0' 8 | -------------------------------------------------------------------------------- /example/react-native.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const pak = require('../package.json'); 3 | 4 | module.exports = { 5 | dependencies: { 6 | [pak.name]: { 7 | root: path.join(__dirname, '..'), 8 | }, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | export { CacheManager } from './ProxyCacheManager'; 2 | export * from './Provider'; 3 | export * from './Hooks'; 4 | export * from './types/type.d'; 5 | export * from './Utils'; 6 | export * from './Libs'; 7 | export * from './user-defined-guard'; 8 | -------------------------------------------------------------------------------- /example/ios/CacheVideoExample/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'CacheVideoExample' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | includeBuild('../node_modules/@react-native/gradle-plugin') 5 | -------------------------------------------------------------------------------- /android/src/newarch/CacheVideoHttpProxySpec.java: -------------------------------------------------------------------------------- 1 | package com.cachevideo; 2 | 3 | import com.facebook.react.bridge.ReactApplicationContext; 4 | 5 | abstract class CacheVideoHttpProxySpec extends NativeCacheVideoHttpProxySpec { 6 | CacheVideoSpec(ReactApplicationContext context) { 7 | super(context); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/ios/CacheVideoExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/CacheVideoExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | nmHoistingLimits: workspaces 3 | 4 | plugins: 5 | - path: scripts/pod-install.cjs 6 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 7 | spec: "@yarnpkg/plugin-interactive-tools" 8 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 9 | spec: "@yarnpkg/plugin-workspace-tools" 10 | 11 | yarnPath: .yarn/releases/yarn-3.6.1.cjs 12 | -------------------------------------------------------------------------------- /src/NativeCacheVideoHttpProxy.ts: -------------------------------------------------------------------------------- 1 | import type { TurboModule } from 'react-native'; 2 | import { TurboModuleRegistry } from 'react-native'; 3 | 4 | export interface Spec extends TurboModule { 5 | start(port: number, serviceName: string): void; 6 | stop(): void; 7 | respond(requestId: number, code: number, type: string, body: string): void; 8 | } 9 | 10 | export default TurboModuleRegistry.getEnforcing('CacheVideoHttpProxy'); 11 | -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const pak = require('../package.json'); 3 | 4 | module.exports = { 5 | presets: ['module:metro-react-native-babel-preset'], 6 | plugins: [ 7 | [ 8 | 'module-resolver', 9 | { 10 | extensions: ['.tsx', '.ts', '.js', '.json'], 11 | alias: { 12 | [pak.name]: path.join(__dirname, '..', pak.source), 13 | }, 14 | }, 15 | ], 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /ios/CacheVideoHttpProxy.h: -------------------------------------------------------------------------------- 1 | 2 | #ifdef RCT_NEW_ARCH_ENABLED 3 | #import "RNCacheVideoHttpProxySpec.h" 4 | 5 | @interface CacheVideoHttpProxy : NSObject { 6 | NSMutableDictionary* _completionBlocks; 7 | } 8 | @end 9 | 10 | #else 11 | #import 12 | 13 | @interface CacheVideoHttpProxy : NSObject { 14 | NSMutableDictionary* _completionBlocks; 15 | } 16 | @end 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /android/src/oldarch/CacheVideoHttpProxySpec.java: -------------------------------------------------------------------------------- 1 | package com.cachevideo; 2 | 3 | import com.facebook.react.bridge.ReactApplicationContext; 4 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 5 | import com.facebook.react.bridge.Promise; 6 | 7 | abstract class CacheVideoHttpProxySpec extends ReactContextBaseJavaModule { 8 | CacheVideoHttpProxySpec(ReactApplicationContext context) { 9 | super(context); 10 | } 11 | 12 | public abstract void multiply(double a, double b, Promise promise); 13 | } 14 | -------------------------------------------------------------------------------- /src/__mock__/react-native-blob-util.js: -------------------------------------------------------------------------------- 1 | // module.exports = { 2 | // __esModule: true, 3 | // default: { 4 | // DocumentDir: jest.fn(), 5 | // config: jest.fn(() => ({ 6 | // fetch: jest.fn(() => ({ 7 | // progress: jest.fn().mockResolvedValue(true), 8 | // })), 9 | // })), 10 | // fs: { 11 | // cp: jest.fn().mockResolvedValue(true), 12 | // dirs: { 13 | // CacheDir: '/mockCacheDir', 14 | // }, 15 | // unlink: jest.fn(), 16 | // }, 17 | // }, 18 | // }; 19 | -------------------------------------------------------------------------------- /example/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /src/Hooks/useIsForeground.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { AppState, type AppStateStatus } from 'react-native'; 3 | 4 | export const useIsForeground = (): boolean => { 5 | const [isForeground, setIsForeground] = useState(true); 6 | 7 | useEffect(() => { 8 | const onChange = (state: AppStateStatus): void => { 9 | setIsForeground(state === 'active'); 10 | }; 11 | const listener = AppState.addEventListener('change', onChange); 12 | return () => listener.remove(); 13 | }, [setIsForeground]); 14 | 15 | return isForeground; 16 | }; 17 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | # You can choose whatever name you want. 2 | # You can share it between projects where you use lefthook. 3 | # Make sure the path is absolute. 4 | rc: ~/.lefthookrc 5 | 6 | pre-commit: 7 | parallel: true 8 | commands: 9 | lint: 10 | files: git diff --name-only @{push} 11 | glob: "*.{js,ts,jsx,tsx}" 12 | run: npx eslint {files} 13 | types: 14 | files: git diff --name-only @{push} 15 | glob: "*.{js,ts, jsx, tsx}" 16 | run: npx tsc --noEmit 17 | commit-msg: 18 | parallel: true 19 | commands: 20 | commitlint: 21 | run: npx commitlint --edit 22 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "33.0.0" 6 | minSdkVersion = 26 7 | compileSdkVersion = 33 8 | targetSdkVersion = 33 9 | 10 | // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. 11 | ndkVersion = "23.1.7779620" 12 | } 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | dependencies { 18 | classpath("com.android.tools.build:gradle") 19 | classpath("com.facebook.react:react-native-gradle-plugin") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Provider/MemoryCacheFreePolicy.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | MemoryCacheDelegate, 3 | MemoryCachePolicyInterface, 4 | } from '../types/type'; 5 | /** 6 | * 7 | * Free policy is a policy that doesn't care about anything, just cache it 8 | */ 9 | export class FreePolicy implements MemoryCachePolicyInterface { 10 | constructor() {} 11 | 12 | clear() {} 13 | 14 | removeEntry(_key: string) {} 15 | 16 | onAccess(_cache: Map, _key: string) {} 17 | 18 | onEvict(_cache: Map, _delegate?: MemoryCacheDelegate) {} 19 | // 20 | get dataSource(): { [key in string]: number } { 21 | return {}; 22 | } 23 | 24 | set dataSource(_data: { [key in string]: number }) {} 25 | } 26 | -------------------------------------------------------------------------------- /src/user-defined-guard/index.ts: -------------------------------------------------------------------------------- 1 | import type { MemoryCachePolicyInterface } from '../types/type'; 2 | 3 | export function isMemoryCachePolicyInterface( 4 | policy: any 5 | ): policy is MemoryCachePolicyInterface { 6 | const hasDataSourceGet = 7 | Object.getOwnPropertyDescriptor(policy, 'dataSource')?.get || 8 | Object.getOwnPropertyDescriptor(Object.getPrototypeOf(policy), 'dataSource') 9 | ?.get; 10 | 11 | const hasDataSourceSet = 12 | Object.getOwnPropertyDescriptor(policy, 'dataSource')?.set || 13 | Object.getOwnPropertyDescriptor(Object.getPrototypeOf(policy), 'dataSource') 14 | ?.set; 15 | 16 | return ( 17 | policy && 18 | typeof policy.onAccess === 'function' && 19 | typeof policy.onEvict === 'function' && 20 | hasDataSourceGet && 21 | hasDataSourceSet 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /example/android/app/src/release/java/com/cachevideoexample/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.cachevideoexample; 8 | 9 | import android.content.Context; 10 | import com.facebook.react.ReactInstanceManager; 11 | 12 | /** 13 | * Class responsible of loading Flipper inside your React Native application. This is the release 14 | * flavor of it so it's empty as we don't want to load Flipper. 15 | */ 16 | public class ReactNativeFlipper { 17 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 18 | // Do nothing as we don't want to initialize Flipper on Release. 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "pipeline": { 4 | "build:android": { 5 | "inputs": [ 6 | "package.json", 7 | "android", 8 | "!android/build", 9 | "src/*.ts", 10 | "src/*.tsx", 11 | "example/package.json", 12 | "example/android", 13 | "!example/android/.gradle", 14 | "!example/android/build", 15 | "!example/android/app/build" 16 | ], 17 | "outputs": [] 18 | }, 19 | "build:ios": { 20 | "inputs": [ 21 | "package.json", 22 | "*.podspec", 23 | "ios", 24 | "src/*.ts", 25 | "src/*.tsx", 26 | "example/package.json", 27 | "example/ios", 28 | "!example/ios/build", 29 | "!example/ios/Pods" 30 | ], 31 | "outputs": [] 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/ios/CacheVideoExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Setup Node.js and install dependencies 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup Node.js 8 | uses: actions/setup-node@v3 9 | with: 10 | node-version-file: .nvmrc 11 | 12 | - name: Cache dependencies 13 | id: yarn-cache 14 | uses: actions/cache@v3 15 | with: 16 | path: | 17 | **/node_modules 18 | .yarn/install-state.gz 19 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**/package.json') }} 20 | restore-keys: | 21 | ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 22 | ${{ runner.os }}-yarn- 23 | 24 | - name: Install dependencies 25 | if: steps.yarn-cache.outputs.cache-hit != 'true' 26 | run: yarn install --immutable 27 | shell: bash 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "paths": { 5 | "react-native-cache-video": ["./src/index"] 6 | }, 7 | "allowJs": true, 8 | "allowUnreachableCode": false, 9 | "allowUnusedLabels": false, 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "jsx": "react", 13 | "lib": ["esnext"], 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "noFallthroughCasesInSwitch": true, 17 | "noImplicitReturns": true, 18 | "noImplicitUseStrict": false, 19 | "noStrictGenericChecks": false, 20 | "noUncheckedIndexedAccess": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "resolveJsonModule": true, 24 | "skipLibCheck": true, 25 | "strict": true, 26 | "target": "esnext", 27 | "verbatimModuleSyntax": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/ios/CacheVideoExample/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | 5 | @implementation AppDelegate 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | self.moduleName = @"CacheVideoExample"; 10 | // You can add your custom initial props in the dictionary below. 11 | // They will be passed down to the ViewController used by React Native. 12 | self.initialProps = @{}; 13 | 14 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 15 | } 16 | 17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 18 | { 19 | #if DEBUG 20 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 21 | #else 22 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 23 | #endif 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /src/Utils/constants.ts: -------------------------------------------------------------------------------- 1 | // 2 | export const SIGNAL_NOT_DOWNLOAD_ACTION = 0x1; 3 | // this is important to avoid evict item that just download and does not access anytime 4 | // we assume that if item is not access in any time, it will be have second chance to access 5 | export const SECOND_CHANCE_TO_COUNT = 0; 6 | export const KEY_PREFIX = 'react-native-cache-video'; 7 | 8 | // application/x-mpegurl 9 | // application/vnd.apple.mpegurl 10 | export const HLS_CONTENT_TYPE = 'application/x-mpegurl'; 11 | export const HLS_VIDEO_TYPE = 'video/MP2T'; 12 | export const HLS_CACHING_RESTART = 'RNCV_HLS_CACHING_RESTART'; 13 | export const QUERY_ORIGIN_PATH = '__hls_origin_url'; 14 | export const LOCALHOST = 'http://127.0.0.1'; 15 | export const VIDEO_EXTENSIONS = ['mp4', 'webm', 'mov', 'avi', 'wmv']; 16 | 17 | export const THRESH_HOLD_TIMEOUT = 300; 18 | export const MIN_PORT = 49152; 19 | export const MAX_PORT = 65535; 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /scripts/pod-install.cjs: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process'); 2 | 3 | module.exports = { 4 | name: 'pod-install', 5 | factory() { 6 | return { 7 | hooks: { 8 | afterAllInstalled(project, options) { 9 | if (process.env.POD_INSTALL === '0') { 10 | return; 11 | } 12 | 13 | if ( 14 | options && 15 | (options.mode === 'update-lockfile' || 16 | options.mode === 'skip-build') 17 | ) { 18 | return; 19 | } 20 | 21 | const result = child_process.spawnSync( 22 | 'yarn', 23 | ['pod-install', 'example/ios'], 24 | { 25 | cwd: project.cwd, 26 | env: process.env, 27 | stdio: 'inherit', 28 | encoding: 'utf-8', 29 | shell: true, 30 | } 31 | ); 32 | 33 | if (result.status !== 0) { 34 | throw new Error('Failed to run pod-install'); 35 | } 36 | }, 37 | }, 38 | }; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Related Issue 8 | 9 | 10 | 11 | ## Type of Change 12 | 13 | - [ ] Bug Fix 14 | - [ ] New Feature 15 | - [ ] Enhancement 16 | - [ ] Documentation Update 17 | - [ ] Other (please specify) 18 | 19 | ## Checklist 20 | 21 | - [ ] I have read the [contribution guidelines](CONTRIBUTING.md) and followed the process. 22 | - [ ] My code follows the coding standards of this project. 23 | - [ ] I have added unit tests for my changes/ I have integrate in example project. 24 | - [ ] I have updated the documentation accordingly. 25 | - [ ] The code builds and passes all existing tests locally. 26 | 27 | ## Screenshots (if applicable) 28 | 29 | 30 | 31 | ## Additional Notes 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # XDE 6 | .expo/ 7 | 8 | # VSCode 9 | .vscode/ 10 | jsconfig.json 11 | 12 | # Xcode 13 | # 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | 32 | # Android/IJ 33 | # 34 | .classpath 35 | .cxx 36 | .gradle 37 | .idea 38 | .project 39 | .settings 40 | local.properties 41 | android.iml 42 | 43 | # Cocoapods 44 | # 45 | example/ios/Pods 46 | 47 | # Ruby 48 | example/vendor/ 49 | 50 | # node.js 51 | # 52 | node_modules/ 53 | npm-debug.log 54 | yarn-debug.log 55 | yarn-error.log 56 | 57 | # BUCK 58 | buck-out/ 59 | \.buckd/ 60 | android/app/libs 61 | android/keystores/debug.keystore 62 | 63 | # Yarn 64 | .yarn/* 65 | !.yarn/patches 66 | !.yarn/plugins 67 | !.yarn/releases 68 | !.yarn/sdks 69 | !.yarn/versions 70 | 71 | # Expo 72 | .expo/ 73 | 74 | # Turborepo 75 | .turbo/ 76 | 77 | # generated by bob 78 | lib/ 79 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Related Issue 8 | 9 | 10 | 11 | ## Type of Change 12 | 13 | - [ ] Bug Fix 14 | - [ ] New Feature 15 | - [ ] Enhancement 16 | - [ ] Documentation Update 17 | - [ ] Other (please specify) 18 | 19 | ## Checklist 20 | 21 | - [ ] I have read the [contribution guidelines](CONTRIBUTING.md) and followed the process. 22 | - [ ] My code follows the coding standards of this project. 23 | - [ ] I have added unit tests for my changes/ I have integrate in example project. 24 | - [ ] I have updated the documentation accordingly. 25 | - [ ] The code builds and passes all existing tests locally. 26 | 27 | ## Screenshots (if applicable) 28 | 29 | 30 | 31 | ## Additional Notes 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 liberty_ng 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /example/ios/CacheVideoExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/cachevideoexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.cachevideoexample; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import com.facebook.react.ReactActivityDelegate; 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate; 7 | 8 | public class MainActivity extends ReactActivity { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | @Override 15 | protected String getMainComponentName() { 16 | return "CacheVideoExample"; 17 | } 18 | 19 | /** 20 | * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link 21 | * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React 22 | * (aka React 18) with two boolean flags. 23 | */ 24 | @Override 25 | protected ReactActivityDelegate createReactActivityDelegate() { 26 | return new DefaultReactActivityDelegate( 27 | this, 28 | getMainComponentName(), 29 | // If you opted-in for the New Architecture, we enable the Fabric Renderer. 30 | DefaultNewArchitectureEntryPoint.getFabricEnabled()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { 4 | CacheManagerProvider, 5 | // FreePolicy, 6 | LFUPolicy, 7 | } from 'react-native-cache-video'; 8 | import ListVideo from './components/VideoList'; 9 | // import SingleVideo from './components/SingleVideo'; 10 | // https://res.cloudinary.com/dannykeane/video/upload/sp_full_hd/q_80:qmax_90,ac_none/v1/dk-memoji-dark.m3u8 11 | 12 | export default function App() { 13 | // 14 | // const _freePolicyRef = React.useRef(new FreePolicy()); 15 | const lfuPolicyRef = React.useRef(new LFUPolicy(5)); 16 | 17 | // return ; 18 | 19 | return ( 20 | 21 | {/* */} 30 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /example/src/components/SingleVideo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DeviceEventEmitter, Image } from 'react-native'; 3 | import { 4 | HLS_CACHING_RESTART, 5 | useAsyncCache, 6 | useIsForeground, 7 | } from 'react-native-cache-video'; 8 | import Video from 'react-native-video'; 9 | 10 | function Component({ uri, thumb }: { uri: string; thumb: string }) { 11 | const { setVideoPlayUrlBy, cachedVideoUrl } = useAsyncCache(); 12 | const isForeground = useIsForeground(); 13 | 14 | React.useEffect(() => { 15 | const listener = DeviceEventEmitter.addListener(HLS_CACHING_RESTART, () => { 16 | setVideoPlayUrlBy(uri); 17 | // setVideoPlayUrlBy( 18 | // 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' 19 | // ); 20 | }); 21 | 22 | return () => { 23 | listener.remove(); 24 | }; 25 | }, [setVideoPlayUrlBy, uri, cachedVideoUrl]); 26 | 27 | return cachedVideoUrl ? ( 28 |

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.cachevideoexample; 8 | 9 | import android.content.Context; 10 | import com.facebook.flipper.android.AndroidFlipperClient; 11 | import com.facebook.flipper.android.utils.FlipperUtils; 12 | import com.facebook.flipper.core.FlipperClient; 13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; 14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; 15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; 16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping; 17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; 18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; 19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; 20 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; 21 | import com.facebook.react.ReactInstanceEventListener; 22 | import com.facebook.react.ReactInstanceManager; 23 | import com.facebook.react.bridge.ReactContext; 24 | import com.facebook.react.modules.network.NetworkingModule; 25 | import okhttp3.OkHttpClient; 26 | 27 | /** 28 | * Class responsible of loading Flipper inside your React Native application. This is the debug 29 | * flavor of it. Here you can add your own plugins and customize the Flipper setup. 30 | */ 31 | public class ReactNativeFlipper { 32 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 33 | if (FlipperUtils.shouldEnableFlipper(context)) { 34 | final FlipperClient client = AndroidFlipperClient.getInstance(context); 35 | 36 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); 37 | client.addPlugin(new DatabasesFlipperPlugin(context)); 38 | client.addPlugin(new SharedPreferencesFlipperPlugin(context)); 39 | client.addPlugin(CrashReporterPlugin.getInstance()); 40 | 41 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); 42 | NetworkingModule.setCustomClientBuilder( 43 | new NetworkingModule.CustomClientBuilder() { 44 | @Override 45 | public void apply(OkHttpClient.Builder builder) { 46 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); 47 | } 48 | }); 49 | client.addPlugin(networkFlipperPlugin); 50 | client.start(); 51 | 52 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized 53 | // Hence we run if after all native modules have been initialized 54 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); 55 | if (reactContext == null) { 56 | reactInstanceManager.addReactInstanceEventListener( 57 | new ReactInstanceEventListener() { 58 | @Override 59 | public void onReactContextInitialized(ReactContext reactContext) { 60 | reactInstanceManager.removeReactInstanceEventListener(this); 61 | reactContext.runOnNativeModulesQueueThread( 62 | new Runnable() { 63 | @Override 64 | public void run() { 65 | client.addPlugin(new FrescoFlipperPlugin()); 66 | } 67 | }); 68 | } 69 | }); 70 | } else { 71 | client.addPlugin(new FrescoFlipperPlugin()); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ios/GCDWebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2019, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerResponse.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerStreamBlock is called to stream the data for the HTTP body. 34 | * The block must return either a chunk of data, an empty NSData when done, or 35 | * nil on error and set the "error" argument which is guaranteed to be non-NULL. 36 | */ 37 | typedef NSData* _Nullable (^GCDWebServerStreamBlock)(NSError** error); 38 | 39 | /** 40 | * The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock 41 | * except the streamed data can be returned at a later time allowing for 42 | * truly asynchronous generation of the data. 43 | * 44 | * The block must call "completionBlock" passing the new chunk of data when ready, 45 | * an empty NSData when done, or nil on error and pass a NSError. 46 | * 47 | * The block cannot call "completionBlock" more than once per invocation. 48 | */ 49 | typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlock completionBlock); 50 | 51 | /** 52 | * The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams 53 | * the body of the HTTP response using a GCD block. 54 | */ 55 | @interface GCDWebServerStreamedResponse : GCDWebServerResponse 56 | @property(nonatomic, copy) NSString* contentType; // Redeclare as non-null 57 | 58 | /** 59 | * Creates a response with streamed data and a given content type. 60 | */ 61 | + (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; 62 | 63 | /** 64 | * Creates a response with async streamed data and a given content type. 65 | */ 66 | + (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block; 67 | 68 | /** 69 | * Initializes a response with streamed data and a given content type. 70 | */ 71 | - (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; 72 | 73 | /** 74 | * This method is the designated initializer for the class. 75 | */ 76 | - (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block; 77 | 78 | @end 79 | 80 | NS_ASSUME_NONNULL_END 81 | -------------------------------------------------------------------------------- /example/ios/CacheVideoExample.xcodeproj/xcshareddata/xcschemes/CacheVideoExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/types/type.d.ts: -------------------------------------------------------------------------------- 1 | // 2 | export interface RequestInterface { 3 | requestId: number; 4 | postData: string; 5 | type: string; 6 | url: string; 7 | data: any; 8 | headers: { 9 | [key in string]: string; 10 | }; 11 | } 12 | 13 | export interface ResponseInterface { 14 | requestId: number; 15 | closed: boolean; 16 | send(code: number, type: string, body: string): void; 17 | json(obj: any, code?: number): void; 18 | html(html: string, code?: number): void; 19 | } 20 | 21 | export type CallBackHandler = ( 22 | request: RequestInterface, 23 | response: ResponseInterface 24 | ) => Promise; 25 | 26 | export interface HttpServer { 27 | multiply(a: number, b: number): Promise; 28 | start(port: number, serviceName: string): void; 29 | stop(): void; 30 | respond(requestId: number, code: number, type: string, body: string): void; 31 | } 32 | 33 | export interface BridgeServerInterface { 34 | serviceName: string; 35 | get: (url: string, callback: CallBackHandler) => void; 36 | post: (url: string, callback: CallBackHandler) => void; 37 | put: (url: string, callback: CallBackHandler) => void; 38 | delete: (url: string, callback: CallBackHandler) => void; 39 | patch: (url: string, callback: CallBackHandler) => void; 40 | use: (callback: CallBackHandler) => void; 41 | listen: (port: number) => void; 42 | stop: () => void; 43 | } 44 | // 45 | export interface MemoryCacheDelegate { 46 | didEvictHandler(key: string, value?: Value): Promise; 47 | } 48 | 49 | export interface MemoryCachePolicyInterface { 50 | // cache policy 51 | onAccess(cache: Map, key: string): void; 52 | onEvict( 53 | cache: Map, 54 | delegate?: MemoryCacheDelegate, 55 | triggerKey?: string 56 | ): void; 57 | clear(): void; 58 | removeEntry(key: string): void; 59 | // 60 | get dataSource(): any; 61 | set dataSource(data: any); 62 | } 63 | 64 | export interface MemoryCacheInterface { 65 | delegate: MemoryCacheDelegate | undefined; 66 | // 67 | export: () => { 68 | lruCachedLocalFiles: [string, Value][]; 69 | referenceBit: any; 70 | }; 71 | load: (jsonStr: string) => void; 72 | clear: () => void; 73 | // 74 | put: (key: string, value: Value) => void; 75 | get: (key: string) => Value | undefined; 76 | has: (key: string) => boolean; 77 | // 78 | syncCache: (key: string, value?: Value) => void; 79 | } 80 | // 81 | export interface FileInterface { 82 | read: (forKey: string) => Promise; 83 | write: (forKey: string, object: string) => Promise; 84 | } 85 | 86 | // 87 | export interface PreCacheInterface { 88 | // pre-caching mechanism 89 | preCacheForList: (listUrl: string[]) => Promise; 90 | preCacheFor: (url: string) => Promise; 91 | cancelCachingList: () => void; 92 | // 93 | delegate?: PreCacheDelegate; 94 | cacheFolder: string; 95 | } 96 | 97 | export interface PreCacheDelegate { 98 | onCachingPlaylistSource: (forKey: string, data: any, folder: string) => void; 99 | contain: (forKey: string) => boolean; 100 | existsFile: (forKey: string) => Promise; 101 | } 102 | 103 | export type SessionTaskOptionsType = { 104 | overwrite?: boolean; 105 | fileCache?: boolean; 106 | path?: string; 107 | wifiOnly?: boolean; 108 | responseEncoding?: 'base64' | 'utf8'; 109 | headers?: { [key in string]: string }; 110 | }; 111 | // 112 | export interface SessionTaskInterface { 113 | dataTask: ( 114 | url: string, 115 | options: SessionTaskOptionsType, 116 | callback?: (data: any, res: any, error?: Error) => void 117 | ) => StatefulPromise; 118 | cancelTask: (url: string) => void; 119 | cancelAllTask: () => void; 120 | } 121 | -------------------------------------------------------------------------------- /ios/GCDWebServer/GCDWebServer/Requests/GCDWebServerDataRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2019, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error GCDWebServer requires ARC 30 | #endif 31 | 32 | #import "GCDWebServerPrivate.h" 33 | 34 | @interface GCDWebServerDataRequest () 35 | @property(nonatomic) NSMutableData* data; 36 | @end 37 | 38 | @implementation GCDWebServerDataRequest { 39 | NSString* _text; 40 | id _jsonObject; 41 | } 42 | 43 | - (BOOL)open:(NSError**)error { 44 | if (self.contentLength != NSUIntegerMax) { 45 | _data = [[NSMutableData alloc] initWithCapacity:self.contentLength]; 46 | } else { 47 | _data = [[NSMutableData alloc] init]; 48 | } 49 | if (_data == nil) { 50 | if (error) { 51 | *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed allocating memory"}]; 52 | } 53 | return NO; 54 | } 55 | return YES; 56 | } 57 | 58 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 59 | [_data appendData:data]; 60 | return YES; 61 | } 62 | 63 | - (BOOL)close:(NSError**)error { 64 | return YES; 65 | } 66 | 67 | - (NSString*)description { 68 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 69 | if (_data) { 70 | [description appendString:@"\n\n"]; 71 | [description appendString:GCDWebServerDescribeData(_data, (NSString*)self.contentType)]; 72 | } 73 | return description; 74 | } 75 | 76 | @end 77 | 78 | @implementation GCDWebServerDataRequest (Extensions) 79 | 80 | - (NSString*)text { 81 | if (_text == nil) { 82 | if ([self.contentType hasPrefix:@"text/"]) { 83 | NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); 84 | _text = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)]; 85 | } else { 86 | GWS_DNOT_REACHED(); 87 | } 88 | } 89 | return _text; 90 | } 91 | 92 | - (id)jsonObject { 93 | if (_jsonObject == nil) { 94 | NSString* mimeType = GCDWebServerTruncateHeaderValue(self.contentType); 95 | if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) { 96 | _jsonObject = [NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL]; 97 | } else { 98 | GWS_DNOT_REACHED(); 99 | } 100 | } 101 | return _jsonObject; 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /ios/GCDWebServer/GCDWebServer/Requests/GCDWebServerFileRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2019, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #if !__has_feature(objc_arc) 29 | #error GCDWebServer requires ARC 30 | #endif 31 | 32 | #import "GCDWebServerPrivate.h" 33 | 34 | @implementation GCDWebServerFileRequest { 35 | int _file; 36 | } 37 | 38 | - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { 39 | if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { 40 | _temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)dealloc { 46 | unlink([_temporaryPath fileSystemRepresentation]); 47 | } 48 | 49 | - (BOOL)open:(NSError**)error { 50 | _file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 51 | if (_file <= 0) { 52 | if (error) { 53 | *error = GCDWebServerMakePosixError(errno); 54 | } 55 | return NO; 56 | } 57 | return YES; 58 | } 59 | 60 | - (BOOL)writeData:(NSData*)data error:(NSError**)error { 61 | if (write(_file, data.bytes, data.length) != (ssize_t)data.length) { 62 | if (error) { 63 | *error = GCDWebServerMakePosixError(errno); 64 | } 65 | return NO; 66 | } 67 | return YES; 68 | } 69 | 70 | - (BOOL)close:(NSError**)error { 71 | if (close(_file) < 0) { 72 | if (error) { 73 | *error = GCDWebServerMakePosixError(errno); 74 | } 75 | return NO; 76 | } 77 | #ifdef __GCDWEBSERVER_ENABLE_TESTING__ 78 | NSString* creationDateHeader = [self.headers objectForKey:@"X-GCDWebServer-CreationDate"]; 79 | if (creationDateHeader) { 80 | NSDate* date = GCDWebServerParseISO8601(creationDateHeader); 81 | if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate : date} ofItemAtPath:_temporaryPath error:error]) { 82 | return NO; 83 | } 84 | } 85 | NSString* modifiedDateHeader = [self.headers objectForKey:@"X-GCDWebServer-ModifiedDate"]; 86 | if (modifiedDateHeader) { 87 | NSDate* date = GCDWebServerParseRFC822(modifiedDateHeader); 88 | if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate : date} ofItemAtPath:_temporaryPath error:error]) { 89 | return NO; 90 | } 91 | } 92 | #endif 93 | return YES; 94 | } 95 | 96 | - (NSString*)description { 97 | NSMutableString* description = [NSMutableString stringWithString:[super description]]; 98 | [description appendFormat:@"\n\n{%@}", _temporaryPath]; 99 | return description; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /ios/GCDWebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2019, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerDataResponse.h" 29 | #import "GCDWebServerHTTPStatusCodes.h" 30 | 31 | NS_ASSUME_NONNULL_BEGIN 32 | 33 | /** 34 | * The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates 35 | * an HTML body from an HTTP status code and an error message. 36 | */ 37 | @interface GCDWebServerErrorResponse : GCDWebServerDataResponse 38 | 39 | /** 40 | * Creates a client error response with the corresponding HTTP status code. 41 | */ 42 | + (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 43 | 44 | /** 45 | * Creates a server error response with the corresponding HTTP status code. 46 | */ 47 | + (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 48 | 49 | /** 50 | * Creates a client error response with the corresponding HTTP status code 51 | * and an underlying NSError. 52 | */ 53 | + (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 54 | 55 | /** 56 | * Creates a server error response with the corresponding HTTP status code 57 | * and an underlying NSError. 58 | */ 59 | + (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 60 | 61 | /** 62 | * Initializes a client error response with the corresponding HTTP status code. 63 | */ 64 | - (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 65 | 66 | /** 67 | * Initializes a server error response with the corresponding HTTP status code. 68 | */ 69 | - (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); 70 | 71 | /** 72 | * Initializes a client error response with the corresponding HTTP status code 73 | * and an underlying NSError. 74 | */ 75 | - (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 76 | 77 | /** 78 | * Initializes a server error response with the corresponding HTTP status code 79 | * and an underlying NSError. 80 | */ 81 | - (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); 82 | 83 | @end 84 | 85 | NS_ASSUME_NONNULL_END 86 | -------------------------------------------------------------------------------- /example/ios/CacheVideoExample/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/Provider/MemoryCacheLFUPolicy.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | MemoryCacheDelegate, 3 | MemoryCachePolicyInterface, 4 | } from '../types/type'; 5 | 6 | import { SECOND_CHANCE_TO_COUNT } from '../Utils/constants'; 7 | import { 8 | isNull, 9 | mergeLargerNumber, 10 | mergeWithCustomCondition, 11 | } from '../Utils/util'; 12 | /** 13 | * 14 | - LRU (Least Recently Used): The least recently used item is evicted. This policy is often used to keep recently accessed items in the cache. 15 | - LFU (Least Frequently Used): The least frequently used item is evicted. This policy is based on the number of accesses to each item. 16 | - LFUSize (Least Frequently Used by Size): The least frequently used item is evicted. This bases the eviction check on cache directory size in MB. 17 | - FIFO (First-In-First-Out): The first item added to the cache is the first one to be evicted. This is a straightforward and easy-to-implement policy. 18 | - Random Replacement: A random item is selected for eviction. This policy does not consider access patterns and can lead to uneven cache performance. 19 | - MRU (Most Recently Used): The most recently used item is evicted. In contrast to LRU, MRU keeps the most recent item in the cache. 20 | - Second-Chance or Clock: Similar to the Second-Chance page replacement algorithm, this policy gives items a second chance before evicting them based on a reference bit. 21 | */ 22 | // LFU (Least Frequently Used) replacement policy 23 | export class LFUPolicy implements MemoryCachePolicyInterface { 24 | private referenceBit: { [key in string]: number }; 25 | private capacity: number; 26 | 27 | constructor(capacity: number) { 28 | this.referenceBit = {} as { 29 | [key in string]: number; 30 | }; 31 | this.capacity = capacity; 32 | } 33 | 34 | clear(): void { 35 | this.referenceBit = {}; 36 | } 37 | 38 | removeEntry(key: string): void { 39 | delete this.referenceBit[key]; 40 | } 41 | 42 | onAccess(cache: Map, key: string) { 43 | // Update access frequency for the item 44 | const value = cache.get(key); 45 | if (value) { 46 | // mixed with LRU 47 | cache.delete(key); 48 | cache.set(key, value); 49 | } 50 | 51 | // access to url, count it if need or give it a chance to be counted 52 | this.referenceBit[key] = isNull(this.referenceBit[key]) 53 | ? SECOND_CHANCE_TO_COUNT 54 | : this.referenceBit[key]! + 1; 55 | } 56 | 57 | onEvict(cache: Map, delegate?: MemoryCacheDelegate) { 58 | if (cache.size < this.capacity) { 59 | return; 60 | } 61 | 62 | // Evict the item with the lowest access frequency 63 | let minFreq = Number.MAX_VALUE; 64 | let lfuKey: string | null = null; 65 | 66 | // Evict the least recently used item (at the end) 67 | for (const key in this.referenceBit) { 68 | if (!cache.has(key)) { 69 | // Only consider keys that actually exist in the cache 70 | delete this.referenceBit[key]; // Clean up stale reference 71 | continue; 72 | } 73 | 74 | const freq = this.referenceBit[key]; 75 | if (freq && freq < minFreq) { 76 | // Consider SECOND_CHANCE_TO_COUNT items if nothing else found 77 | if (freq !== SECOND_CHANCE_TO_COUNT || lfuKey === null) { 78 | minFreq = freq; 79 | lfuKey = key; 80 | } 81 | } 82 | } 83 | 84 | if (lfuKey) { 85 | const value = cache.get(lfuKey); 86 | cache.delete(lfuKey); 87 | delete this.referenceBit[lfuKey]; 88 | delegate && delegate.didEvictHandler(lfuKey, value); 89 | } else if (cache.size >= this.capacity) { 90 | // If we couldn't find anything to evict but still need space, 91 | // evict the first item (oldest by insertion order) 92 | const firstKey = cache.keys().next().value; 93 | if (firstKey) { 94 | const value = cache.get(firstKey); 95 | cache.delete(firstKey); 96 | delete this.referenceBit[firstKey]; 97 | delegate && delegate.didEvictHandler(firstKey, value); 98 | } 99 | } 100 | } 101 | // 102 | get dataSource(): { [key in string]: number } { 103 | return this.referenceBit; 104 | } 105 | 106 | set dataSource(data: { [key in string]: number }) { 107 | const newDataSource = mergeWithCustomCondition( 108 | this.referenceBit, 109 | data, 110 | mergeLargerNumber 111 | ); 112 | this.referenceBit = newDataSource; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /ios/GCDWebServer/GCDWebServer/Responses/GCDWebServerDataResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2019, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerResponse.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body 34 | * of the HTTP response from memory. 35 | */ 36 | @interface GCDWebServerDataResponse : GCDWebServerResponse 37 | @property(nonatomic, copy) NSString* contentType; // Redeclare as non-null 38 | 39 | /** 40 | * Creates a response with data in memory and a given content type. 41 | */ 42 | + (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type; 43 | 44 | /** 45 | * This method is the designated initializer for the class. 46 | */ 47 | - (instancetype)initWithData:(NSData*)data contentType:(NSString*)type; 48 | 49 | @end 50 | 51 | @interface GCDWebServerDataResponse (Extensions) 52 | 53 | /** 54 | * Creates a data response from text encoded using UTF-8. 55 | */ 56 | + (nullable instancetype)responseWithText:(NSString*)text; 57 | 58 | /** 59 | * Creates a data response from HTML encoded using UTF-8. 60 | */ 61 | + (nullable instancetype)responseWithHTML:(NSString*)html; 62 | 63 | /** 64 | * Creates a data response from an HTML template encoded using UTF-8. 65 | * See -initWithHTMLTemplate:variables: for details. 66 | */ 67 | + (nullable instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; 68 | 69 | /** 70 | * Creates a data response from a serialized JSON object and the default 71 | * "application/json" content type. 72 | */ 73 | + (nullable instancetype)responseWithJSONObject:(id)object; 74 | 75 | /** 76 | * Creates a data response from a serialized JSON object and a custom 77 | * content type. 78 | */ 79 | + (nullable instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type; 80 | 81 | /** 82 | * Initializes a data response from text encoded using UTF-8. 83 | */ 84 | - (nullable instancetype)initWithText:(NSString*)text; 85 | 86 | /** 87 | * Initializes a data response from HTML encoded using UTF-8. 88 | */ 89 | - (nullable instancetype)initWithHTML:(NSString*)html; 90 | 91 | /** 92 | * Initializes a data response from an HTML template encoded using UTF-8. 93 | * 94 | * All occurences of "%variable%" within the HTML template are replaced with 95 | * their corresponding values. 96 | */ 97 | - (nullable instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; 98 | 99 | /** 100 | * Initializes a data response from a serialized JSON object and the default 101 | * "application/json" content type. 102 | */ 103 | - (nullable instancetype)initWithJSONObject:(id)object; 104 | 105 | /** 106 | * Initializes a data response from a serialized JSON object and a custom 107 | * content type. 108 | */ 109 | - (nullable instancetype)initWithJSONObject:(id)object contentType:(NSString*)type; 110 | 111 | @end 112 | 113 | NS_ASSUME_NONNULL_END 114 | -------------------------------------------------------------------------------- /ios/GCDWebServer/GCDWebServer/Core/GCDWebServerFunctions.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2019, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | /** 37 | * Converts a file extension to the corresponding MIME type. 38 | * If there is no match, "application/octet-stream" is returned. 39 | * 40 | * Overrides allow to customize the built-in mapping from extensions to MIME 41 | * types. Keys of the dictionary must be lowercased file extensions without 42 | * the period, and the values must be the corresponding MIME types. 43 | */ 44 | NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* _Nullable overrides); 45 | 46 | /** 47 | * Add percent-escapes to a string so it can be used in a URL. 48 | * The legal characters ":@/?&=+" are also escaped to ensure compatibility 49 | * with URL encoded forms and URL queries. 50 | */ 51 | NSString* _Nullable GCDWebServerEscapeURLString(NSString* string); 52 | 53 | /** 54 | * Unescapes a URL percent-encoded string. 55 | */ 56 | NSString* _Nullable GCDWebServerUnescapeURLString(NSString* string); 57 | 58 | /** 59 | * Extracts the unescaped names and values from an 60 | * "application/x-www-form-urlencoded" form. 61 | * http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 62 | */ 63 | NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form); 64 | 65 | /** 66 | * On OS X, returns the IPv4 or IPv6 address as a string of the primary 67 | * connected service or nil if not available. 68 | * 69 | * On iOS, returns the IPv4 or IPv6 address as a string of the WiFi 70 | * interface if connected or nil otherwise. 71 | */ 72 | NSString* _Nullable GCDWebServerGetPrimaryIPAddress(BOOL useIPv6); 73 | 74 | /** 75 | * Converts a date into a string using RFC822 formatting. 76 | * https://tools.ietf.org/html/rfc822#section-5 77 | * https://tools.ietf.org/html/rfc1123#section-5.2.14 78 | */ 79 | NSString* GCDWebServerFormatRFC822(NSDate* date); 80 | 81 | /** 82 | * Converts a RFC822 formatted string into a date. 83 | * https://tools.ietf.org/html/rfc822#section-5 84 | * https://tools.ietf.org/html/rfc1123#section-5.2.14 85 | * 86 | * @warning Timezones other than GMT are not supported by this function. 87 | */ 88 | NSDate* _Nullable GCDWebServerParseRFC822(NSString* string); 89 | 90 | /** 91 | * Converts a date into a string using IOS 8601 formatting. 92 | * http://tools.ietf.org/html/rfc3339#section-5.6 93 | */ 94 | NSString* GCDWebServerFormatISO8601(NSDate* date); 95 | 96 | /** 97 | * Converts a ISO 8601 formatted string into a date. 98 | * http://tools.ietf.org/html/rfc3339#section-5.6 99 | * 100 | * @warning Only "calendar" variant is supported at this time and timezones 101 | * other than GMT are not supported either. 102 | */ 103 | NSDate* _Nullable GCDWebServerParseISO8601(NSString* string); 104 | 105 | /** 106 | * Removes "//", "/./" and "/../" components from path as well as any trailing slash. 107 | */ 108 | NSString* GCDWebServerNormalizePath(NSString* path); 109 | 110 | #ifdef __cplusplus 111 | } 112 | #endif 113 | 114 | NS_ASSUME_NONNULL_END 115 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup 18 | uses: ./.github/actions/setup 19 | 20 | - name: Lint files 21 | run: yarn lint 22 | 23 | - name: Typecheck files 24 | run: yarn typecheck 25 | 26 | test: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | 32 | - name: Setup 33 | uses: ./.github/actions/setup 34 | 35 | - name: Run unit tests 36 | run: yarn test --maxWorkers=2 --coverage 37 | 38 | build-library: 39 | runs-on: ubuntu-latest 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v3 43 | 44 | - name: Setup 45 | uses: ./.github/actions/setup 46 | 47 | - name: Build package 48 | run: yarn prepare 49 | 50 | build-android: 51 | runs-on: ubuntu-latest 52 | env: 53 | TURBO_CACHE_DIR: .turbo/android 54 | steps: 55 | - name: Checkout 56 | uses: actions/checkout@v3 57 | 58 | - name: Setup 59 | uses: ./.github/actions/setup 60 | 61 | - name: Cache turborepo for Android 62 | uses: actions/cache@v3 63 | with: 64 | path: ${{ env.TURBO_CACHE_DIR }} 65 | key: ${{ runner.os }}-turborepo-android-${{ hashFiles('**/yarn.lock') }} 66 | restore-keys: | 67 | ${{ runner.os }}-turborepo-android- 68 | 69 | - name: Check turborepo cache for Android 70 | run: | 71 | TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status") 72 | 73 | if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then 74 | echo "turbo_cache_hit=1" >> $GITHUB_ENV 75 | fi 76 | 77 | - name: Install JDK 78 | if: env.turbo_cache_hit != 1 79 | uses: actions/setup-java@v3 80 | with: 81 | distribution: 'zulu' 82 | java-version: '11' 83 | 84 | - name: Finalize Android SDK 85 | if: env.turbo_cache_hit != 1 86 | run: | 87 | /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null" 88 | 89 | - name: Cache Gradle 90 | if: env.turbo_cache_hit != 1 91 | uses: actions/cache@v3 92 | with: 93 | path: | 94 | ~/.gradle/wrapper 95 | ~/.gradle/caches 96 | key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }} 97 | restore-keys: | 98 | ${{ runner.os }}-gradle- 99 | 100 | - name: Build example for Android 101 | run: | 102 | yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" 103 | 104 | build-ios: 105 | runs-on: macos-latest 106 | env: 107 | TURBO_CACHE_DIR: .turbo/ios 108 | steps: 109 | - name: Checkout 110 | uses: actions/checkout@v3 111 | 112 | - name: Setup 113 | uses: ./.github/actions/setup 114 | 115 | - name: Cache turborepo for iOS 116 | uses: actions/cache@v3 117 | with: 118 | path: ${{ env.TURBO_CACHE_DIR }} 119 | key: ${{ runner.os }}-turborepo-ios-${{ hashFiles('**/yarn.lock') }} 120 | restore-keys: | 121 | ${{ runner.os }}-turborepo-ios- 122 | 123 | - name: Check turborepo cache for iOS 124 | run: | 125 | TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status") 126 | 127 | if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then 128 | echo "turbo_cache_hit=1" >> $GITHUB_ENV 129 | fi 130 | 131 | - name: Cache cocoapods 132 | if: env.turbo_cache_hit != 1 133 | id: cocoapods-cache 134 | uses: actions/cache@v3 135 | with: 136 | path: | 137 | **/ios/Pods 138 | key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }} 139 | restore-keys: | 140 | ${{ runner.os }}-cocoapods- 141 | 142 | - name: Install cocoapods 143 | if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true' 144 | run: | 145 | yarn pod-install example/ios 146 | env: 147 | NO_FLIPPER: 1 148 | 149 | - name: Build example for iOS 150 | run: | 151 | yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" 152 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-cache-video", 3 | "version": "0.3.0", 4 | "description": "support cache video type mp4, mov, hls for cdn-preload-cached video in scrollview, flatlist", 5 | "main": "lib/commonjs/index", 6 | "module": "lib/module/index", 7 | "types": "lib/typescript/src/index.d.ts", 8 | "react-native": "src/index", 9 | "source": "src/index", 10 | "files": [ 11 | "src", 12 | "lib", 13 | "android", 14 | "ios", 15 | "cpp", 16 | "*.podspec", 17 | "!ios/build", 18 | "!android/build", 19 | "!android/gradle", 20 | "!android/gradlew", 21 | "!android/gradlew.bat", 22 | "!android/local.properties", 23 | "!**/__tests__", 24 | "!**/__fixtures__", 25 | "!**/__mocks__", 26 | "!**/.*" 27 | ], 28 | "scripts": { 29 | "example": "yarn workspace react-native-cache-video-example", 30 | "test": "jest", 31 | "typecheck": "tsc --noEmit", 32 | "lint": "eslint \"**/*.{js,ts,tsx}\"", 33 | "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", 34 | "prepare": "bob build", 35 | "release": "release-it" 36 | }, 37 | "keywords": [ 38 | "react-native", 39 | "ios", 40 | "android" 41 | ], 42 | "repository": "https://github.com/nguyenvanphituoc/react-native-cache-video", 43 | "author": "liberty_ng (https://github.com/nguyenvanphituoc)", 44 | "license": "MIT", 45 | "bugs": { 46 | "url": "https://github.com/nguyenvanphituoc/react-native-cache-video/issues" 47 | }, 48 | "homepage": "https://github.com/nguyenvanphituoc/react-native-cache-video#readme", 49 | "publishConfig": { 50 | "registry": "https://registry.npmjs.org/" 51 | }, 52 | "devDependencies": { 53 | "@commitlint/config-conventional": "^17.0.2", 54 | "@evilmartians/lefthook": "^1.5.0", 55 | "@react-native/eslint-config": "^0.72.2", 56 | "@release-it/conventional-changelog": "^5.0.0", 57 | "@types/jest": "^28.1.2", 58 | "@types/react": "~17.0.21", 59 | "@types/react-native": "0.70.0", 60 | "commitlint": "^17.0.2", 61 | "del-cli": "^5.0.0", 62 | "eslint": "^8.4.1", 63 | "eslint-config-prettier": "^8.5.0", 64 | "eslint-plugin-prettier": "^4.0.0", 65 | "jest": "^28.1.1", 66 | "pod-install": "^0.1.0", 67 | "prettier": "^2.0.5", 68 | "react": "18.2.0", 69 | "react-native": "0.72.17", 70 | "react-native-blob-util": "^0.19.2", 71 | "react-native-builder-bob": "^0.23.1", 72 | "react-native-url-polyfill": "^2.0.0", 73 | "release-it": "^15.0.0", 74 | "turbo": "^1.10.7", 75 | "typescript": "^5.0.2" 76 | }, 77 | "resolutions": { 78 | "@types/react": "17.0.21" 79 | }, 80 | "peerDependencies": { 81 | "react": "*", 82 | "react-native": "*", 83 | "react-native-blob-util": "*", 84 | "react-native-url-polyfill": "*" 85 | }, 86 | "workspaces": [ 87 | "example" 88 | ], 89 | "packageManager": "yarn@3.6.1", 90 | "engines": { 91 | "node": ">= 18.0.0" 92 | }, 93 | "jest": { 94 | "preset": "react-native", 95 | "modulePathIgnorePatterns": [ 96 | "/example/node_modules", 97 | "/lib/" 98 | ] 99 | }, 100 | "commitlint": { 101 | "extends": [ 102 | "@commitlint/config-conventional" 103 | ] 104 | }, 105 | "release-it": { 106 | "git": { 107 | "commitMessage": "chore: release ${version}", 108 | "tagName": "v${version}" 109 | }, 110 | "npm": { 111 | "publish": true 112 | }, 113 | "github": { 114 | "release": true 115 | }, 116 | "plugins": { 117 | "@release-it/conventional-changelog": { 118 | "preset": "angular" 119 | } 120 | } 121 | }, 122 | "eslintConfig": { 123 | "root": true, 124 | "extends": [ 125 | "@react-native", 126 | "prettier" 127 | ], 128 | "rules": { 129 | "prettier/prettier": [ 130 | "error", 131 | { 132 | "quoteProps": "consistent", 133 | "singleQuote": true, 134 | "tabWidth": 2, 135 | "trailingComma": "es5", 136 | "useTabs": false 137 | } 138 | ] 139 | } 140 | }, 141 | "eslintIgnore": [ 142 | "node_modules/", 143 | "lib/" 144 | ], 145 | "prettier": { 146 | "quoteProps": "consistent", 147 | "singleQuote": true, 148 | "tabWidth": 2, 149 | "trailingComma": "es5", 150 | "useTabs": false 151 | }, 152 | "react-native-builder-bob": { 153 | "source": "src", 154 | "output": "lib", 155 | "targets": [ 156 | "commonjs", 157 | "module", 158 | [ 159 | "typescript", 160 | { 161 | "project": "tsconfig.build.json" 162 | } 163 | ] 164 | ] 165 | }, 166 | "codegenConfig": { 167 | "name": "RNCacheVideoHttpProxySpec", 168 | "type": "modules", 169 | "jsSrcsDir": "src" 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /android/src/main/java/com/cachevideo/httpServer/Server.java: -------------------------------------------------------------------------------- 1 | package com.cachevideo.httpServer; 2 | 3 | import fi.iki.elonen.NanoHTTPD; 4 | import fi.iki.elonen.NanoHTTPD.Response; 5 | import fi.iki.elonen.NanoHTTPD.Response.Status; 6 | import com.facebook.react.bridge.ReactContext; 7 | import com.facebook.react.bridge.Arguments; 8 | import com.facebook.react.bridge.ReadableMap; 9 | import com.facebook.react.bridge.WritableMap; 10 | import com.facebook.react.modules.core.DeviceEventManagerModule; 11 | 12 | import java.io.ByteArrayInputStream; 13 | import java.io.IOException; 14 | import java.io.UnsupportedEncodingException; 15 | import java.nio.charset.Charset; 16 | import java.nio.charset.CharsetEncoder; 17 | import java.util.Base64; 18 | import java.util.Map; 19 | import java.util.Set; 20 | import java.util.HashMap; 21 | import java.util.Random; 22 | import java.util.logging.Level; 23 | 24 | import androidx.annotation.Nullable; 25 | import android.util.Log; 26 | 27 | public class Server extends NanoHTTPD { 28 | private static final String TAG = "HttpServer"; 29 | private static final String SERVER_EVENT_ID = "httpServerResponseReceived"; 30 | 31 | private ReactContext reactContext; 32 | private Map responses; 33 | 34 | public Server(ReactContext context, int port) { 35 | super(port); 36 | reactContext = context; 37 | responses = new HashMap<>(); 38 | } 39 | 40 | @Override 41 | public void start() throws IOException { 42 | super.start(); 43 | 44 | Log.d(TAG, "Server started"); 45 | } 46 | 47 | @Override 48 | public void stop() { 49 | super.stop(); 50 | responses.clear(); 51 | Log.d(TAG, "Stop server"); 52 | } 53 | 54 | @Override 55 | public Response serve(IHTTPSession session) { 56 | Log.d(TAG, "Request received!"); 57 | 58 | Method method = session.getMethod(); 59 | String uri = session.getUri(); 60 | Map files = new HashMap<>(); 61 | 62 | Random rand = new Random(); 63 | String requestId = String.format("%d:%d", System.currentTimeMillis(), rand.nextInt(1000000)); 64 | 65 | if (Method.GET.equals(method)) { 66 | 67 | WritableMap request; 68 | try { 69 | request = fillRequestMap(session, requestId); 70 | } catch (Exception e) { 71 | return newFixedLengthResponse( 72 | Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, e.getMessage() 73 | ); 74 | } 75 | 76 | this.sendEvent(reactContext, SERVER_EVENT_ID, request); 77 | } else { 78 | 79 | } 80 | 81 | while (responses.get(requestId) == null) { 82 | try { 83 | Thread.sleep(10); 84 | } catch (Exception e) { 85 | Log.d(TAG, "Exception while waiting: " + e); 86 | } 87 | } 88 | Response response = responses.get(requestId); 89 | responses.remove(requestId); 90 | return response; 91 | } 92 | 93 | public void respond(String requestId, int code, String type, String body) { 94 | try { 95 | // 96 | // String decodedString = new String(decodedBytes, ASCII_ENCODING); 97 | byte[] bytes = Base64.getDecoder().decode(body); 98 | // cause newFixedLengthResponse will find encoding in type, and default encoding is ASCII_ENCODING 99 | // responses.put(requestId, this.newFixedLengthResponse(Status.lookup(code), type, body)); 100 | ContentType contentType = new ContentType(type); 101 | responses.put(requestId, newFixedLengthResponse(Status.lookup(code), contentType.getContentTypeHeader(), new ByteArrayInputStream(bytes), bytes.length)); 102 | } catch (Exception e) { 103 | Log.d(TAG, "Exception while waiting: " + e); 104 | } 105 | } 106 | 107 | private WritableMap fillRequestMap(IHTTPSession session, String requestId) throws Exception { 108 | Method method = session.getMethod(); 109 | WritableMap request = Arguments.createMap(); 110 | 111 | request.putString("url", session.getUri() + "?" + session.getQueryParameterString()); 112 | request.putString("type", method.name()); 113 | request.putString("requestId", requestId); 114 | 115 | session.getHeaders().forEach( 116 | (key, value) -> request.putString(key, value) 117 | ); 118 | 119 | Map files = new HashMap<>(); 120 | session.parseBody(files); 121 | if (files.size() > 0) { 122 | request.putString("postData", files.get("postData")); 123 | } 124 | 125 | return request; 126 | } 127 | 128 | private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) { 129 | reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params); 130 | } 131 | 132 | 133 | } 134 | -------------------------------------------------------------------------------- /ios/GCDWebServer/GCDWebServer/Responses/GCDWebServerFileResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2019, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "GCDWebServerResponse.h" 29 | 30 | NS_ASSUME_NONNULL_BEGIN 31 | 32 | /** 33 | * The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body 34 | * of the HTTP response from a file on disk. 35 | * 36 | * It will automatically set the contentType, lastModifiedDate and eTag 37 | * properties of the GCDWebServerResponse according to the file extension and 38 | * metadata. 39 | */ 40 | @interface GCDWebServerFileResponse : GCDWebServerResponse 41 | @property(nonatomic, copy) NSString* contentType; // Redeclare as non-null 42 | @property(nonatomic) NSDate* lastModifiedDate; // Redeclare as non-null 43 | @property(nonatomic, copy) NSString* eTag; // Redeclare as non-null 44 | 45 | /** 46 | * Creates a response with the contents of a file. 47 | */ 48 | + (nullable instancetype)responseWithFile:(NSString*)path; 49 | 50 | /** 51 | * Creates a response like +responseWithFile: and sets the "Content-Disposition" 52 | * HTTP header for a download if the "attachment" argument is YES. 53 | */ 54 | + (nullable instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment; 55 | 56 | /** 57 | * Creates a response like +responseWithFile: but restricts the file contents 58 | * to a specific byte range. 59 | * 60 | * See -initWithFile:byteRange: for details. 61 | */ 62 | + (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range; 63 | 64 | /** 65 | * Creates a response like +responseWithFile:byteRange: and sets the 66 | * "Content-Disposition" HTTP header for a download if the "attachment" 67 | * argument is YES. 68 | */ 69 | + (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment; 70 | 71 | /** 72 | * Initializes a response with the contents of a file. 73 | */ 74 | - (nullable instancetype)initWithFile:(NSString*)path; 75 | 76 | /** 77 | * Initializes a response like +responseWithFile: and sets the 78 | * "Content-Disposition" HTTP header for a download if the "attachment" 79 | * argument is YES. 80 | */ 81 | - (nullable instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment; 82 | 83 | /** 84 | * Initializes a response like -initWithFile: but restricts the file contents 85 | * to a specific byte range. This range should be set to (NSUIntegerMax, 0) for 86 | * the full file, (offset, length) if expressed from the beginning of the file, 87 | * or (NSUIntegerMax, length) if expressed from the end of the file. The "offset" 88 | * and "length" values will be automatically adjusted to be compatible with the 89 | * actual size of the file. 90 | * 91 | * This argument would typically be set to the value of the byteRange property 92 | * of the current GCDWebServerRequest. 93 | */ 94 | - (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range; 95 | 96 | /** 97 | * This method is the designated initializer for the class. 98 | * 99 | * If MIME type overrides are specified, they allow to customize the built-in 100 | * mapping from extensions to MIME types. Keys of the dictionary must be lowercased 101 | * file extensions without the period, and the values must be the corresponding 102 | * MIME types. 103 | */ 104 | - (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(nullable NSDictionary*)overrides; 105 | 106 | @end 107 | 108 | NS_ASSUME_NONNULL_END 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-cache-video 2 | 3 | Support cache video type when playing in Video component 4 | 5 | - [x] Download and read video/ hls video from cache 6 | - [x] Cache policy for video for number of video in file system 7 | - [ ] Cache policy for hls video 8 | - [ ] hls caching for dynamic url ( cloudfront) 9 | - [x] Byte-Range Support for Segments 10 | - [ ] Pre caching for list/ while scrolling 11 | 12 | ## Installation 13 | 14 | with npm 15 | 16 | ```sh 17 | npm install react-native-blob-util react-native-url-polyfill react-native-cache-video 18 | ``` 19 | 20 | with yarn 21 | 22 | ```sh 23 | yarn add react-native-blob-util react-native-url-polyfill react-native-cache-video 24 | ``` 25 | 26 | ## Usage 27 | 28 | Support play with [react-native-video](https://github.com/react-native-video/react-native-video.git) 29 | 30 | You can run [example](example/) folder. I give two case using with single video item for viewing in detail and using with list of video 31 | 32 | Simple using without provider - don't care about your app memory 33 | 34 | - You can clear react-native-cache-video folder in your file system by access cacheManager.cacheFolder from useProxyCacheManager 35 | - This case does not support HLS caching, you need use with Provider 36 | 37 | ```js 38 | // your customize video component 39 | import { useAsyncCache } from 'react-native-cache-video'; 40 | 41 | const { setVideoPlayUrlBy, cachedVideoUrl } = useAsyncCache(); 42 | 43 | React.useEffect(() => { 44 | setVideoPlayUrlBy(uri); 45 | }, [setVideoPlayUrlBy, uri]); 46 | 47 |