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

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

作者: 风风风筝 | 来源:发表于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://github.com/sigma18/ExoPlayerhttps://github.com/si...

  • Android 本地缓存ACache的简单使用和工具类

    设置缓存数据: 获取缓存数据: 举个栗子? 存数据 读取数据:判断当下缓存是否为空,若是则不再读取,否则读取数据

  • Spring Cache

    缓存命中率 即从缓存中读取数据的次数 与 总读取次数的比率,命中率越高越好:命中率 = 从缓存中读取次数 / (总...

  • Spring Boot集成cache

    缓存简介 工作机制是:先从缓存中读取数据,如果没有再从慢速设备上读取实际数据(数据也会存入缓存);缓存什么:那些经...

  • 触发式的缓存一致性方式

    当读取缓存的时候,如果缓存里没有相关数据,则执行相关的业务逻辑,构造缓存数据存入到缓存系统; 当与缓存项相关的资源...

  • Spring Cache

    缓存理解 让数据更接近使用者 基本机制:先从缓存中读取数据,如果没有再从慢速设备上读取实际数据(数据也会存入缓存)...

  • Spring 缓存框架

    缓存是让数据更接近于使用者;工作机制是先从缓存中读取数据,如果没有再从慢速设备上读取实际数据(数据也会存入缓存);...

  • 亿级流量网站架构核心技术【笔记】(二)

    九、应用级缓存 A.缓存简介 1.先从缓存中读取数据,如果没有,再从慢速设备上读取实际数据并同步到缓存 2.经常读...

  • iOS开发本地缓存(数据离线缓存、读取、释放)

    iOS开发本地缓存(数据离线缓存、读取、释放)_异客_新浪博客

  • Java - 内存框架设计与实现

    一、使用缓存 对缓存的常用操作描述 查询时,先读取缓存,如果缓存中没有数据,则触发真正的数据获取,如果缓存中有数据...

网友评论

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

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