indexedDB本地缓存视频等大文件

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- <script src="./indexedDBStore.js"></script> -->
    <script>
        class IndexedDBStore {
            /**
             * @param {string} dbName - 数据库名称
             * @param {number} version - 数据库版本
             * @param {Object} storeConfig - 存储配置 { name: 存储名称, keyPath: 主键, indexes: 索引数组 }
             */
            constructor(dbName, version, storeConfig) {
                this.dbName = dbName;
                this.version = version;
                this.storeConfig = storeConfig;
                this.db = null;
            }

            /**
             * 打开数据库连接
             * @returns {Promise<IDBDatabase>}
             */
            open() {
                return new Promise((resolve, reject) => {
                    if (this.db) return resolve(this.db);

                    const request = indexedDB.open(this.dbName, this.version);

                    request.onupgradeneeded = (event) => {
                        const db = event.target.result;
                        if (!db.objectStoreNames.contains(this.storeConfig.name)) {
                            const store = db.createObjectStore(this.storeConfig.name, {
                                keyPath: this.storeConfig.keyPath,
                                autoIncrement: this.storeConfig.autoIncrement || false
                            });
                            this.storeConfig.indexes?.forEach(index => {
                                store.createIndex(index.name, index.keyPath, index.options);
                            });
                        }
                    };

                    request.onsuccess = (event) => {
                        this.db = event.target.result;
                        resolve(this.db);
                    };

                    request.onerror = (event) => {
                        reject(`IndexedDB error: ${event.target.error}`);
                    };
                });
            }

            /**
             * 添加数据
             * @param {Object} data - 要存储的数据
             * @returns {Promise<IDBValidKey>}
             */
            async add(data) {
                const db = await this.open();
                return new Promise((resolve, reject) => {
                    const transaction = db.transaction(this.storeConfig.name, 'readwrite');
                    const store = transaction.objectStore(this.storeConfig.name);
                    const request = store.add(data);

                    request.onsuccess = () => resolve(request.result);
                    request.onerror = () => reject(request.error);
                });
            }

            /**
             * 根据主键获取数据
             * @param {IDBValidKey} key - 主键值
             * @returns {Promise<Object>}
             */
            async get(key) {
                const db = await this.open();
                return new Promise((resolve, reject) => {
                    const transaction = db.transaction(this.storeConfig.name, 'readonly');
                    const store = transaction.objectStore(this.storeConfig.name);
                    const request = store.get(key);

                    request.onsuccess = () => resolve(request.result);
                    request.onerror = () => reject(request.error);
                });
            }

            /**
             * 更新数据
             * @param {Object} data - 要更新的数据(必须包含主键)
             * @returns {Promise<IDBValidKey>}
             */
            async update(data) {
                const db = await this.open();
                return new Promise((resolve, reject) => {
                    const transaction = db.transaction(this.storeConfig.name, 'readwrite');
                    const store = transaction.objectStore(this.storeConfig.name);
                    const request = store.put(data);

                    request.onsuccess = () => resolve(request.result);
                    request.onerror = () => reject(request.error);
                });
            }

            /**
             * 删除数据
             * @param {IDBValidKey} key - 要删除的主键值
             * @returns {Promise<void>}
             */
            async delete(key) {
                const db = await this.open();
                return new Promise((resolve, reject) => {
                    const transaction = db.transaction(this.storeConfig.name, 'readwrite');
                    const store = transaction.objectStore(this.storeConfig.name);
                    const request = store.delete(key);

                    request.onsuccess = () => resolve();
                    request.onerror = () => reject(request.error);
                });
            }

            /**
             * 查询数据(通过索引)
             * @param {string} indexName - 索引名称
             * @param {IDBValidKey} value - 查询值
             * @returns {Promise<Array>}
             */
            async queryByIndex(indexName, value) {
                const db = await this.open();
                return new Promise((resolve, reject) => {
                    const transaction = db.transaction(this.storeConfig.name, 'readonly');
                    const store = transaction.objectStore(this.storeConfig.name);
                    const index = store.index(indexName);
                    const request = index.getAll(value);

                    request.onsuccess = () => resolve(request.result);
                    request.onerror = () => reject(request.error);
                });
            }

            /**
             * 获取所有数据
             * @returns {Promise<Array>}
             */
            async getAll() {
                const db = await this.open();
                return new Promise((resolve, reject) => {
                    const transaction = db.transaction(this.storeConfig.name, 'readonly');
                    const store = transaction.objectStore(this.storeConfig.name);
                    const request = store.getAll();

                    request.onsuccess = () => resolve(request.result);
                    request.onerror = () => reject(request.error);
                });
            }

            /**
             * 清空存储
             * @returns {Promise<void>}
             */
            async clear() {
                const db = await this.open();
                return new Promise((resolve, reject) => {
                    const transaction = db.transaction(this.storeConfig.name, 'readwrite');
                    const store = transaction.objectStore(this.storeConfig.name);
                    const request = store.clear();

                    request.onsuccess = () => resolve();
                    request.onerror = () => reject(request.error);
                });
            }
        }


        // 使用示例
        const dbConfig = {
            name: 'data',
            keyPath: 'id',
            autoIncrement: true,
            indexes: [
                { name: 'url', keyPath: 'url', options: { unique: true } },
            ]
            // indexes: [
            //     { name: 'email', keyPath: 'email', options: { unique: true } },
            //     { name: 'age', keyPath: 'age' }
            // ]
        };

        const dataDB = new IndexedDBStore('MyAppDB', 1, dbConfig);

        // 初始化数据库
        dataDB.open()
            .then(() => console.log('Database initialized'))
            .catch(err => console.error('Initialization failed:', err));

        // 添加用户
        async function addData(data) {
            try {
                await dataDB.add(data);
                console.log('Data added');
            } catch (error) {
                console.error('Add failed:', error);
            }
        }

        // 查询所有用户
        async function getAllData() {
            try {
                const data = await dataDB.getAll();
                console.log('All data:', data);
                return data;
            } catch (error) {
                console.error('Query failed:', error);
            }
        }

        async function getData(url) {
            try {
                const data = await dataDB.queryByIndex('url', url)
                console.log('All data:', data);
                return data;
            } catch (error) {
                console.error('Query failed:', error);
            }

        }
        let urlList = [
        "https://vd3.bdstatic.com/mda-ni8zjpqumj0zq8wu/sc/cae_h264/1662767674990457142/mda-ni8zjpqumj0zq8wu.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1739475858-0-0-5ab2399f0e9fb1ebe93cc98ee943b9d1&bcevod_channel=searchbox_feed&cr=2&cd=0&pd=1&pt=3&logid=2658451388&vid=11439834437747274135&klogid=2658451388&abtest=132219_1",
        // "https://vd3.bdstatic.com/mda-qdd9c4qibyb9ewxr/sc/cae_h264/1713181094724751050/mda-qdd9c4qibyb9ewxr.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1739475666-0-0-4a72becac12543d6f2039ea61a9fbdb4&bcevod_channel=searchbox_feed&cr=2&cd=0&pd=1&pt=3&logid=2465962025&vid=2939798464947657648&klogid=2465962025&abtest=132219_1",
        // "https://vd3.bdstatic.com/mda-pbpjq8k4b30i7dta/sc/cae_h264/1677247731713061770/mda-pbpjq8k4b30i7dta.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1739475438-0-0-f9784748f27a84e7eaf4a78d379cfad4&bcevod_channel=searchbox_feed&cr=2&cd=0&pd=1&pt=3&logid=2238334780&vid=9432685572508656171&klogid=2238334780&abtest=132219_1",
        // "https://vd3.bdstatic.com/mda-rasicbaqwftn3x7w/sc/cae_h264/1737998688828146561/mda-rasicbaqwftn3x7w.mp4?v_from_s=hkapp-haokan-hbf&auth_key=1739474489-0-0-319d6636018c016959767f44a20648f0&bcevod_channel=searchbox_feed&cr=2&cd=0&pd=1&pt=3&logid=1289777015&vid=3911988374435305462&klogid=1289777015&abtest=132219_1",
        
    ]
        urlList.forEach(item => {
            addVideo("/?url="+item)
        })

        function addVideo(url) {
            getData(url).then(res => {
                console.log(res, 'res');
                let videoItem = document.createElement("video");
                videoItem.width = 400
                videoItem.height = 300
                videoItem.controls = true
                videoItem.muted = true
                videoItem.autoplay = true
                document.body.appendChild(videoItem)
                // setTimeout(() => {
                //     videoItem.muted = false; 
                //     videoItem.play().then(() => {
                //       console.log('视频自动播放成功');
                //     }).catch((error) => {
                //       console.error('自动播放失败:', error);
                //     });// 取消静音
                // }, 2000)
                if (res.length > 0) {
                    videoItem.src = URL.createObjectURL(res[0].data)
                } else {
                    videoItem.src = url
                    fetch(url).then(res => {
                        if (!res.ok) {
                            throw new Error('Network response was not ok ' + res.statusText);
                        }
                        return res.blob();
                    })
                        .then(blob => {
                            console.log(blob);
                            addData({ url: url, data: blob });
                        })
                        .catch(error => {
                            console.error('There was a problem with the fetch operation:', error);
                        });
                }
            })


        }// getAllData()

    </script>
</body>

</html>

node 代理处理跨域

javascript 复制代码
const http = require('http');
const https = require('https');
const url = require('url');
const path = require('path');
const fs = require('fs');

// 静态资源目录
const staticDir = path.join(__dirname, 'public');

// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
  // 允许跨域
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

  // 处理预检请求(OPTIONS)
  if (req.method === 'OPTIONS') {
    res.writeHead(204);
    res.end();
    return;
  }

  // 解析请求 URL
  const parsedUrl = url.parse(req.url, true);
  const videoUrl = parsedUrl.query.url;

  // 处理视频代理请求
  if (videoUrl) {
    const client = videoUrl.startsWith('https') ? https : http;

    client.get(videoUrl, (videoRes) => {
      res.setHeader('Content-Type', videoRes.headers['content-type']);
      if (videoRes.headers['content-length']) {
        res.setHeader('Content-Length', videoRes.headers['content-length']);
      }
      videoRes.pipe(res);
    }).on('error', (err) => {
      console.error('Error fetching video:', err);
      res.writeHead(500, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Failed to fetch video' }));
    });
    return;
  }

  // 处理静态资源请求
  if (req.method !== 'GET' && req.method !== 'HEAD') {
    res.writeHead(405);
    res.end('Method Not Allowed');
    return;
  }

  let pathname = parsedUrl.pathname;
  pathname = pathname === '/' ? '/index.html' : pathname;

  // 构造安全路径
  const requestedPath = path.join(staticDir, pathname);
  const absolutePath = path.resolve(requestedPath);

  // 路径安全检查
  if (!absolutePath.startsWith(path.resolve(staticDir))) {
    res.writeHead(403);
    res.end('Forbidden');
    return;
  }

  fs.stat(absolutePath, (err, stats) => {
    if (err) {
      res.writeHead(err.code === 'ENOENT' ? 404 : 500);
      res.end();
      return;
    }

    if (stats.isDirectory()) {
      const indexPath = path.join(absolutePath, 'index.html');
      fs.stat(indexPath, (err, indexStats) => {
        if (!err && indexStats.isFile()) {
          serveFile(indexPath, res);
        } else {
          res.writeHead(403);
          res.end('Directory access forbidden');
        }
      });
    } else {
      serveFile(absolutePath, res);
    }
  });
});

// 文件服务函数
function serveFile(filePath, res) {
  const ext = path.extname(filePath).toLowerCase();
  const contentType = getContentType(ext);
  const stream = fs.createReadStream(filePath);

  res.setHeader('Content-Type', contentType);
  stream.pipe(res);
  
  stream.on('error', () => {
    res.writeHead(500);
    res.end('Internal Server Error');
  });
}

// MIME 类型映射
function getContentType(ext) {
  const mimeTypes = {
    '.html': 'text/html',
    '.css': 'text/css',
    '.js': 'application/javascript',
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.gif': 'image/gif',
    '.json': 'application/json',
    '.txt': 'text/plain',
  };
  return mimeTypes[ext] || 'application/octet-stream';
}

// 启动服务器
const PORT = 3000;
server.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
  console.log(`Static files from: ${staticDir}`);
});

单独处理

javascript 复制代码
const http = require('http');
const https = require('https');
const url = require('url');

// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
  // 允许跨域
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

  // 处理预检请求(OPTIONS)
  if (req.method === 'OPTIONS') {
    res.writeHead(204); // No Content
    res.end();
    return;
  }

  // 解析请求 URL
  const queryObject = url.parse(req.url, true).query;
  const videoUrl = queryObject.url; // 从查询参数中获取视频 URL

  if (!videoUrl) {
    res.writeHead(400, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Missing video URL parameter' }));
    return;
  }

  // 根据视频 URL 的协议选择 http 或 https 模块
  const client = videoUrl.startsWith('https') ? https : http;

  // 发起请求到目标视频服务器
  client.get(videoUrl, (videoRes) => {
    // 设置响应头
    res.setHeader('Content-Type', videoRes.headers['content-type']);
    res.setHeader('Content-Length', videoRes.headers['content-length']);

    // 流式传输视频数据
    videoRes.pipe(res);
  }).on('error', (err) => {
    console.error('Error fetching video:', err);
    res.writeHead(500, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Failed to fetch video' }));
  });
});

// 启动服务器
const PORT = 3000;
server.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});
相关推荐
早起的年轻人1 小时前
Docket Desktop 安装redis 并设置密码
数据库·redis·缓存
草字2 小时前
vue,vue3 keepalive没有效果,无法缓存页面include无效,keep-alive
前端·vue.js·缓存
oioihoii2 小时前
深入理解 C++17 的缓存行接口
java·c++·缓存
qw9492 小时前
Redis(高阶篇)03章——缓存双写一致性之更新策略探讨
数据库·redis·缓存
神马都会亿点点的毛毛张2 小时前
【SpringBoot教程】SpringBoot整合Caffeine本地缓存及Spring Cache注解的使用
java·spring boot·后端·spring·缓存·caffeine
清风细雨_林木木3 小时前
Mac 清理缓存,提高内存空间
macos·缓存
Macdo_cn3 小时前
Screen Wonders for Mac v3.3.1 3D屏保应用 支持M、Intel芯片
macos·音视频
TravisBytes4 小时前
Redis如何解决热Key问题
数据库·redis·缓存
苏三福7 小时前
rk3588/3576板端编译程序无法运行视频推理
arm开发·音视频
江同学_7 小时前
RTSP场景下RTP协议详解及音视频打包全流程
音视频