美文网首页
在线播放的数据读取与缓存

在线播放的数据读取与缓存

作者: 风风风筝 | 来源:发表于2018-02-28 16:55 被阅读69次

    https://github.com/sigma18/ExoPlayer
    https://github.com/sigma18/MediaData

    1. player 不关心数据来源

    player 完全不想关心数据的来源,不想关心是来自网络还是磁盘

    内存中维护一个队列 MemoryCacheManager(每一个元素包装为 BufferItem),player 只管从 MemoryCacheManager 读取数据(从队列中取出一个 BufferItem),当 MemoryCacheManager 中没有数据时,player 读取数据的线程被挂起,当 MemoryCacheManager 有数据时会被唤醒

    2. MemoryCacheManager 队列的数据来源

    启动一个线程(DataFetcher)循环读取数据,优先从磁盘读取,磁盘没有则从网络读取,每次从网络读到的数据写入磁盘。每次读到的数据包装为 BufferItem 然后存入 MemoryCacheManager

    3、用户体验和流量浪费的平衡

    给 MemoryCacheManager 设置一个占用内存的上限(mCacheSizeLimit),当 MemoryCacheManager 队列占用的内存达到我们设置的上限时,线程 DataFetcher 被挂起,当 MemoryCacheManager 有数据被取走时会被唤醒
    通过调整 mCacheSizeLimit 从而在用户体验和服务器流量、用户手机流量之间找到一个平衡点

    4、磁盘缓存碎片

    4.1 碎片的产生

    当播放一个未缓存过的视频时,假设已从网络读取数据 10000 字节,此时拖动进度条,player 请求数据的 position 发生改变,假设 position = 20000,然后继续播放到结束,假设又从从网络读取数据 5000 字节。那么磁盘应该存在 2 段缓存,第一段是从 0~10000,第二段是从 20000~25000

    4.2 碎片记录

    为记录这些磁盘缓存碎片,设计了2个数据库表:主表 Video 和 子表 VideoPart
    Video 主要包含:id, url, size, cache_dir, last_use_time
    VideoPart 主要包含:id, video_id, start, end, cache_name

    cache_dir 对应磁盘上为此 video 创建的文件夹名字,cache_name 对应缓存碎片的文件名

    4.3 碎片合并

    假设当前待写入的碎片是 videoPart,待写入的数据长度为 writeLength,每次要写入的时候,查询数据库找出存在叠加区域的碎片 nextPart(如果有的话)

    long end = videoPart.getEnd() + writeLength;
    if (nextPart.getStart() <= end && end < nextPart.getEnd()) 
    

    如果找到 nextPart,算出叠加的区域

    int redundantLength = (int) (end - nextPart.getStart());
    

    然后写入有用的数据再合并 2 个文件

    writeLength = writeLength - redundantLength;
    FileUtils.write(accessFile, buffer, position, writeLength);
    FileUtils.merge(accessFile, nextFile);
    

    5. 无缝衔接从网络和磁盘读取数据

    1. 判断磁盘上是否有包含 position 的碎片,如果没有则进入 3,有则进入 2
    2. 从磁盘读取,直到这一段缓存到结尾,position += x(假设这一段缓存有 x 字节)
    3. 建立网络连接,设置 RANGE bytes=position
    4. 从网络读取(假设请求到 y 字节),position += y
    5. 判断磁盘上是否有包含 position 的碎片,没有则进入 4,有则进入 2

    6. 移动网络

    每次从网络读取时,如果当前是移动网络
    如果是不被允许的,则会发出通知并挂起线程(DataFetcher),也就停止读数据

    7. 脏数据

    在一些特殊情况下,可能会产生无效的缓存,比如

    1. 在播放过程中,进程被杀死
    2. 用户删掉 SD 卡的缓存文件

    脏数据包括

    1. 无效的数据库记录,因为对应的文件不存在,或者 end - start ≠ file.length()
    2. 无效的缓存文件,因为没有数据库记录使用它

    所以在每次应用启动的时候,对所有 Video 记录进行校验,删掉脏数据
    在每次新开一个 Video 读写磁盘缓存时,再对这个 Video 记录进行一次校验

    [DiskCacheManager.java]
    
    public synchronized void open(String url) {
        ...
        mVideo = mDbHelper.queryVideoByUrl(mUrl);
        if (mVideo != null) {
            ...
            checkCache(mVideo);
        }
    }
    

    8. 磁盘缓存 LRU

    每次写入磁盘后都要判断缓存文件的大小总和是否超出上限,如果超出则删掉 last_use_time 最小的那个 Video 记录和文件,但是至少保留当前正在使用的这个 Video

    [DiskCacheManager.java]
    
    public synchronized void open(String url) {
        ....
        mVideo = mDbHelper.queryVideoByUrl(mUrl);
        if (mVideo != null) {
            ....
            mVideo.setLastUseTime(System.currentTimeMillis());
            mDbHelper.update(mVideo);
            checkCache(mVideo);
        }
    }
    
    private void trimToSize(VideoPart videoPart) {
        while (mCacheSize > MAX_CACHE_SIZE) {
            Video video = mDbHelper.queryEldestVideo();
            if (video.getId() == videoPart.getVideoId()) {
                return;
            }
            File videoDir = new File(VideoDataSource.getInstance().getCacheDir(), video.getCacheDir());
            if (videoDir.exists()) {
                File[] videoPartFiles = videoDir.listFiles();
                if (videoPartFiles != null) {
                    for (File videoPartFile : videoPartFiles) {
                        mCacheSize -= videoPartFile.length();
                        videoPartFile.delete();
                    }
                }
                videoDir.delete();
            }
            mDbHelper.deleteVideo(video.getId());
        }
    }
    

    9. 数据库快照

    很显然在这个应用场景里,数据库增删改的次数远小于查询,所以没必要每次都去查询数据库,只需要查询一次并放在内存即可。但要确保两者的一致性。

    [DiskCacheManager.java]
    
    private List<VideoPart> mVideoPartList;
    

    每次读写大概率是使用上一次的 VideoPart,不必每次都遍历 mVideoPartList 进行查找

    [DiskCacheManager.java]
    
    private VideoPart mReadVideoPart;
    private RandomAccessFile mReadFile;
    
    private VideoPart mWriteVideoPart;
    private RandomAccessFile mWriteFile;
    

    相关文章

      网友评论

          本文标题:在线播放的数据读取与缓存

          本文链接:https://www.haomeiwen.com/subject/kevyxftx.html