TheWidlarzGroup / react-native-video

A <Video /> component for react-native
http://thewidlarzgroup.github.io/react-native-video/
MIT License
7.14k stars 2.88k forks source link

Add caching #99

Closed jamesfzhang closed 2 years ago

jamesfzhang commented 8 years ago

How do you cache a video that's downloaded over the network?

ababol commented 8 years ago

Any news? I am interested by this too :+1:

cancan101 commented 8 years ago

This might be a good solution: https://github.com/johanneslumpe/react-native-fs

thsig commented 8 years ago

Another approach would be to write downloaded assets to a temp directory and load them from there when available. I've only worked with the Obj C part of the native code, but this approach would work there (I assume it could work on the Java side as well). Then the relevant cleanup logic would have to be added to the component lifecycle, to make sure react-native-video doesn't start filling up disk space.

Wouldn't it be better to think of this as an implementation detail of the native code and maintain the current API?

thsig commented 8 years ago

I'd be up for implementing this for iOS to start with. My initial proposal is that caching is disabled by default, but requested on a per-clip basis like so:

// Cache video locally (write to temp directory)
<Video source={{uri: "https://foo.com/clip1.mp4", cache: true}} ... />

// Don't cache video
<Video source={{uri: "https://foo.com/clip1.mp4"}} ... />

The cache option would be ignored for local files, since it's only relevant for files loaded over the network.

The native code would then first look for the asset on disk, loading it from there if found, or otherwise load it remotely as usual.

There are at least a couple of decisions that have to be made around this:

  1. Setting the total maximum size (in MB) of the files cached by the library. This could be a fixed size (30MB?), perhaps with some configuration option to override the default max size on the React class?
  2. When should the temp dir be cleared/deleted? Does it have to be cleaned out at all if the max size of the temp dir is reasonable? Or should the each instance of the component delete assets older than e.g. 10 minutes when it's initialized? This caching should probably be mostly ephemeral, e.g. for switching to full-screen mode or changing the UI hierarchy while conceptually maintaining the same underlying "player context".

Would be great to get some feedback on this. I'll probably start work on this soon at any rate since I need this for an app I'm working on, so the sooner the better.

jamesfzhang commented 8 years ago

Sounds like a good approach!

  1. It would be nice to be able to configure how much to cache.
  2. It would also be nice to configure a TTL on the cache, and the cache would get purged either when its full or when the TTL is met.

Not sure how I feel about this, but how about being able to configure the params like this?

// Use smart defaults (30MB cache size & purge in 10 minute )
<Video source={{uri: "https://foo.com/clip1.mp4", cache: true } ... />

// Configure cache to 50MB & purge in 1 hour
<Video source={{uri: "https://foo.com/clip1.mp4", cache: { size: 50, expiresIn: 3600 }} ... />
thsig commented 8 years ago

@jamesfzhang The thing with that approach is that these parameters should probably be set globally for the app. For example, if one writes something like this:

<View>
  <Video source={{uri: "https://foo.com/clip1.mp4", cache: { size: 50, expiresIn: 3600 }} ... />
  <Video source={{uri: "https://foo.com/clip1.mp4", cache: { size: 40, expiresIn: 4800 }} ... />
</View>

Which set of cache settings should be used for caching https://foo.com/clip1.mp4? This could work fine if users are instructed to always use the same cache settings for the same clip/s, but it feels a bit hacky — I think it would be better to globally configure this somewhere so that it applies to all instances of Video, but I'm not sure what's the best way to go about that. Modifying the class directly?

jamesfzhang commented 8 years ago

I was under the impression that the configurations would be set per video, as opposed to global. I imagine there are some videos you want to cache and others you don't.

But I guess just using global configs would be fine too? Maybe you'd need to add a VideoConfiguration class?

thsig commented 8 years ago

They could be set per video, but it's not clear which settings should take precedence when there are several Video instances mounted using the same clip, which is OK (if we can assume that users stick to providing the same config via props to each instance) but not ideal. Otherwise there's no way to know e.g. whether https://foo.com/clip1.mp4 should be invalidated in 3600 seconds or 4800 seconds above.

Something like this would work (maybe not particularly pretty, but then again it only has to be written once):

// require this file instead of require('react-native-video') where Video is to be used
var Video = class extends (require('react-native-video').default) {
  cacheOptions() { return {size: 50, expiresIn: 3600, key: 'settings'} };
}
module.exports = Video;

Then the cache settings would be applied globally through the app, and only once. This could also be done several times with different settings, if the user of the library is confident that there won't be conflicts (i.e. two Video components caching the same clip with differing settings). E.g. for two different contexts within the app with different kinds of clips, with different caching needs:

// LongClipPlayer.js
var LongVideo = class extends (require('react-native-video').default) {
  cacheOptions() { return {size: 50, expiresIn: 3600, key: 'longVideoCache'} };
}
module.exports = LongVideo;

// ShortClipPlayer.js
var ShortVideo = class extends (require('react-native-video').default) {
  cacheOptions() { return {size: 10, expiresIn: 1200, key: 'shortVideoCache'} };
}
module.exports = ShortVideo;

Then the implementation would put videos created with key in a temp folder distinct from those used for other keys, so that the invalidation logic only operates on the appropriate caches.

Thoughts on this?

thsig commented 8 years ago

The second case (multiple configs) may be overkill to start with, we could just go for a single global configuration for starters. The defaults should be fine for most users too, so I don't think this should add to the complexity of using the library, unless more control is needed.

ababol commented 8 years ago

A really easy solution is the one that @cancan101 mentioned, using react-native-fs, but yeah as @thsig mentioned there will huge issue with the usage of the disk space if we are just storing everything without an expire limit.

Otherwise I agree with @thsig , let's keep it simple first, nah?

Here is the code that is doing the thing to cache Image, but it is permanent storage, it is never removed :/ https://github.com/mohebifar/react-native-ximage/blob/master/src/Storage.js#L64-L66

One super easy solution to clean the cache is to make a function which check all cached video of the specific application/folder and which deletes the old videos.

Something like this:

module.exports.cleanCache = function (cacheKey, time) {
  const videos = readCache(cacheKey);
  videos.forEach((video) => {
    // is my video older than `time`?
    if (isMyVideoOld(video, time) {
      deleteVideo(video);
    }
  });
}

The function will be accessible for the developer and we don't have to care if he/she is going to use it. By default, we can just disable the cache and if the developer wants to use it, he should be aware that he has to use this function to clean it.

thsig commented 8 years ago

Maybe it would be better to separate the caching implementation from react-native-video, but have an API for react-native-video to receive pre-loaded AVAsset-s (and the equivalent for Android) under the covers. It seems to me that this library should primarily concern itself with playback and offload other concerns to other libraries where possible.

What about something like this?

<Video
  source={{uri: "https://foo.com/clip1.mp4"}}
  cacheName='StevesCache' />

When receiving new sources, the native code would publish a notification to the channel named cacheName (e.g. via the NSNotificationCenter system in iOS, and the Java equivalent on Android), containing the source uri/s which would then be received by the StevesCache native module class (or whatever the class is named - it's decoupled from the channel name provided).

The cache module implementation would then publish a message to the ReactNativeVideo channel, where the payload would be an array whose elements are either AVAsset* (or the Java equivalent) or nil, depending on whether the requested URI was found by the cache implementation's cache (in the same order as the source array sent to it in the previous step).

I'm suggesting an array assuming that multiple-video playback will be added to the library at some point (whether my branch is merged or not, this is probably on the roadmap anyway), so that would pre-empt having to change the API later, possibly after other libraries start depending on it.

Then react-native-video would treat the cached assets as fully buffered and proceed from there, buffering non-cached assets as usual.

Thoughts, @brentvatne? We might need functionality like this soon, so I'd probably be up for writing the initial iOS implementation. I'm also very open to alternate implementation ideas.

sjchmiela commented 8 years ago

I have a feeling there may be a better way – proxying requests to server. One could set himself as AVAssetResourceLoader and then check in his own cache if such video has been cached or else load from URL. This approach has been described:

Based on uri we could initialize under the hood one cache for every custom protocol, eg.:

<Video
  source={{uri: "stevesCache://foo.com/clip1.mp4"}}
/>

...

<Video
  source={{uri: "globalCache://foo.com/clip1.mp4"}}
/>

Maybe we could use uri as stevesCache://https://foo.com... to be able to specify target request, I don't know how NSURL would handle such URL.

Customizing cache capacity would require calls to some kind of Cache.setCacheCapacity('stevesCache', 40 * 1024 * 1024)

hongri commented 8 years ago

Hi, all:

I want to know when to publish this function? It waste a lot of flow now.

getnashty commented 8 years ago

Any updates here? @thsig, definitely think you should take over the repo :)

brentvatne commented 8 years ago

good point @getnashty! @thsig I have added you as a collaborator on this repo. let me know your npm username and I can give you npm publish access, brentvatne at gmail dot com

duhseekoh commented 7 years ago

This hasn't been touched for a while, but since iOS natively supports offline caching now it's worth reviving: https://developer.apple.com/reference/avfoundation/avassetdownloadurlsession/1650938-makeassetdownloadtask

Video explaining the process. Starts at about 16:20: https://developer.apple.com/videos/play/wwdc2016/504/

Looks like this could all be handled behind the scenes in this package, and all the user has to decide is if the video should be cached and provide it the key name + download location that would be needed to look the task up again.

From the video: image

image He points out that the video can be played at ANY time during the download process, and it's smart enough to use whats been downloaded already and continue downloading to the cache as the video plays.

Sidenote: A sibling package to this that only does the downloading could be helpful as well. Would be used to load videos even without a react-native-video on the screen. (app is in background, preload video, etc..)

ndbroadbent commented 7 years ago

@duhseekoh That's fantastic, thanks for posting that! I should pay more attention to updates.

I've been working on a native iOS app with Swift (which I started a few years ago), and now I'm going rewrite it in React Native. I have been wanting to cache downloaded videos for a very long time, but it was going to be a ton of work. But it looks like AVAssetDownloadTask changes everything, and video caching should be pretty easy to implement now. (Although I'm nervous about Java and Windows.)

I'm going to need this for my app, so I would like to contribute caching for iOS. I might be able to figure out Android, too. Worst case would be that caching only works on iOS and is just ignored on other platforms.

Maybe it would be better to separate the caching implementation from react-native-video

@thsig - I'm not sure about that, I think most developers would love to pass cache={true}, and just have it use some sane defaults. E.g. A LRU cache with a max size of 100MB would be great for my app (lots of short videos), and I think it would probably work well for most video sharing apps. And probably a default TTL of 24 hours, with a customizable cacheTTL.

In the case where you have several Video instances playing the same video, then the last one would have precedence, and we could just show a yellow box warning during development.

jlongster commented 7 years ago

@duhseekoh From the looks of it AVAssetDownloadTask requires HLS, right? So you can't use it for any arbitrary video. That seems different than what this issue is about.

juergengunz commented 7 years ago

any updates on this?

vvavepacket commented 7 years ago

if we use react-native-fs approach,, does it mean the video has to be downloaded fully, before react-native-video can access it? Please correct me if I'm wrong,, but what I want to happen is while the video is playing (and at the same time downloading) in the backgroud, the video file is cache somewhere.

LeeZC commented 7 years ago

waiting publish this function ...

SuhairZain commented 7 years ago

Is anyone working on this feature?

raschan commented 7 years ago

Would also love to have caching in react-native-video

janreyho commented 7 years ago

+1

itaydr commented 6 years ago

+1

freddiecabrera commented 6 years ago

+1

rnowm commented 6 years ago

+1

kaifah commented 6 years ago

等待更新

hoorsa commented 6 years ago

+1

jeffzing commented 6 years ago

keep follwing...坐等更新

fajardolar commented 6 years ago

+1

wassgha commented 6 years ago

+1

iMagdy commented 6 years ago

+1

cyjcxy commented 6 years ago

缓存功能还没有实现吗?苦等半年了~~

ebouJ commented 6 years ago

+1

VrajSolanki commented 6 years ago

+1

michaelfranz commented 6 years ago

My app's video resources are all online, hence viewable by using the form: {uri: 'https:...'} But now I would like to offer the user the option to download videos for offline viewing. Yet this appears to be incompatible with RN's way of handling resources of this kind - or with react-native-video's current support for addressing locally stored video content. This is kind of related to the question of caching. Does anyone have any suggestions?

n1ru4l commented 6 years ago

For those interested I started implementing caching for ios here: https://github.com/n1ru4l/react-native-video/tree/implement-ios-caching

freddiecabrera commented 6 years ago

🙌🏽

On Thu, Mar 1, 2018 at 2:45 PM, Laurin Quast notifications@github.com wrote:

For those interested I started implementing caching for ios here: https://github.com/n1ru4l/react-native-video/tree/implement-ios-caching

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/react-native-community/react-native-video/issues/99#issuecomment-369757434, or mute the thread https://github.com/notifications/unsubscribe-auth/AKEVzci2WCGkU7HjROi_Ccnp8I-UScvqks5taHoAgaJpZM4GG5sc .

-- Best Regards, Freddie Cabrera

n1ru4l commented 6 years ago

Here you go: https://github.com/react-native-community/react-native-video/pull/955

hkdahal commented 6 years ago

Hey guys, any update on this? Thanks in advance!

n1ru4l commented 6 years ago

@hkdahal You could help out by testing my implementation. The instructions are listed in the Pull Request (#955).

DavitVosk commented 6 years ago

Hello. Did anyone solve the problem of caching video?

rnowm commented 6 years ago

@DavitVosk I ended up using this library: https://github.com/kfiroo/react-native-cached-image with that PR: https://github.com/kfiroo/react-native-cached-image/pull/110/commits/31ad016afabcbe42e3c1eb058a0d9a57b0fb615f which allows you to cache all file extensions, instead of only images.

DavitVosk commented 6 years ago

@rnowm Thanks for an answer. Can you please give me an example how you use that package with react-native-video?

n1ru4l commented 6 years ago

@DavitVosk You could use my fork (https://github.com/react-native-community/react-native-video/pull/955) published to npm as @n1ru4l/react-native-video. Sadly nobody is giving me feedback so it seems like this will not be merged any time soon.

I am using my fork in a production application, so I can ensure you it works. However it is not up to date with the upstream changes, I will try to rebase/merge master soon.

If you try my fork feel free to provide feedback and possible enhancements 😊

DavitVosk commented 6 years ago

@n1ru4l thanks for an answer. So what does it differ from the real rn-video package?

n1ru4l commented 6 years ago

@DavitVosk You can read the differences in the pull request #955

DavitVosk commented 6 years ago

@n1ru4l, it looks a great solution if it works))). Now trying your package but receive several errors like no such file or directory: .../node_modules/react-native-video/ios/RCTVideoPlayerViewController.m, .../node_modules/react-native-video/ios/RCTVideo.m, etc... Can you please help me to understand what was the problem. So I had already installed rev-video package, so I unlinked it and now linking your package manually

DavitVosk commented 6 years ago

ok I see that react-native-video folder is not directly situated in node/modules - it is located in n1ru4l folder in node/modules. And that was giving the above-mentioned errors. Its route should be changed... I copied the all react-native-video folder directly into node/modules and the errors went away. But now I get the following error: SPTPersistentCache/SPTPersistentCache.h file not found