Skip to content

25時作業風景

25時作業風景(25ji Sagyo Fukei)是一个沉浸式学习工具,提供番茄钟计时、背景视频和 Project SEKAI 音乐播放。

项目信息

功能特性

番茄钟计时

  • 25 分钟工作 + 5 分钟休息 - 经典番茄钟
  • 自定义时长 - 支持自定义工作/休息时间
  • 自动循环 - 完成后自动开始下一轮
  • 通知提醒 - 浏览器通知 + 音效

背景视频

  • 时间同步 - 自动根据现实时间选择背景
  • HLS 流式播放 - 高质量视频,低带宽消耗
  • 循环播放 - 无缝循环
  • 音量控制 - 独立音量调节

音乐播放

  • Project SEKAI 音乐库 - 200+ 首歌曲
  • 搜索和筛选 - 按标题、作曲家、角色搜索
  • 播放列表 - 创建自定义播放列表
  • 歌词显示 - 同步显示歌词(规划中)

在线状态

  • 实时在线人数 - 显示当前在线用户
  • 活动统计 - 今日学习时长、完成番茄钟数
  • 排行榜 - 学习时长排行(规划中)

技术架构

前端架构

index.html
├── css/
│   ├── style.css
│   └── themes/          # 主题样式
├── js/
│   ├── main.js          # 入口文件
│   ├── timer.js         # 番茄钟逻辑
│   ├── video.js         # 视频播放
│   ├── music.js         # 音乐播放
│   ├── ui.js            # UI 更新
│   └── storage.js       # 本地存储
└── assets/
    ├── videos/          # 背景视频
    ├── music/           # 音乐文件
    └── sounds/          # 音效

视频播放

使用 HLS.js 播放 HLS 流:

javascript
import Hls from 'hls.js';

const video = document.getElementById('background-video');
const hls = new Hls();

hls.loadSource('https://assets.nightcord.de5.net/videos/nightcord.m3u8');
hls.attachMedia(video);

hls.on(Hls.Events.MANIFEST_PARSED, () => {
  video.play();
});

音乐播放

使用 Web Audio API:

javascript
const audioContext = new AudioContext();
const source = audioContext.createBufferSource();
const gainNode = audioContext.createGain();

source.connect(gainNode);
gainNode.connect(audioContext.destination);

// 加载音频
const response = await fetch('https://assets.nightcord.de5.net/music/song.mp3');
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);

source.buffer = audioBuffer;
source.start();

数据存储

使用 IndexedDB 存储音乐数据:

javascript
const db = await openDB('25ji', 1, {
  upgrade(db) {
    db.createObjectStore('music', { keyPath: 'id' });
    db.createObjectStore('playlists', { keyPath: 'id' });
  }
});

// 存储音乐
await db.put('music', {
  id: 1,
  title: 'Tell Your World',
  artist: 'kz (livetune)',
  duration: 126
});

// 查询音乐
const music = await db.getAll('music');

本地开发

克隆项目

bash
git clone https://github.com/25-ji-code-de/25ji.git
cd 25ji

运行

bash
# 使用 HTTP 服务器
python3 -m http.server 8000
# 访问 http://localhost:8000

配置

编辑 js/config.js

javascript
const CONFIG = {
  API_URL: 'https://api.nightcord.de5.net',
  ASSETS_URL: 'https://assets.nightcord.de5.net',
  WS_URL: 'wss://25ji.nightcord.de5.net/ws'
};

部署

Cloudflare Pages

bash
# 推送到 GitHub
git push origin main

# Cloudflare Pages 自动部署

构建配置:

  • 构建命令: 无(静态站点)
  • 输出目录: /
  • 环境变量: 无

自定义域名

在 Cloudflare Pages 设置中添加自定义域名 25ji.nightcord.de5.net

使用指南

开始学习

  1. 访问 25ji.nightcord.de5.net
  2. 点击"开始"按钮启动番茄钟
  3. 专注学习 25 分钟
  4. 休息 5 分钟
  5. 重复

自定义设置

  1. 点击设置图标
  2. 调整工作时长(默认 25 分钟)
  3. 调整休息时长(默认 5 分钟)
  4. 选择背景视频
  5. 选择音乐播放列表

播放音乐

  1. 点击音乐图标
  2. 搜索或浏览歌曲
  3. 点击播放
  4. 调整音量

API 集成

获取音乐数据

javascript
async function loadMusicData() {
  const response = await fetch('https://api.nightcord.de5.net/sekai/music_data.json');
  const data = await response.json();

  // 存储到 IndexedDB
  const db = await openDB('25ji', 1);
  for (const song of data.songs) {
    await db.put('music', song);
  }

  return data.songs;
}

上报学习时长

javascript
async function reportStudyTime(duration) {
  await fetch('https://25ji.nightcord.de5.net/api/stats', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      userId: currentUser.id,
      duration,
      timestamp: Date.now()
    })
  });
}

性能优化

视频预加载

javascript
// 预加载下一个时段的视频
function preloadNextVideo() {
  const nextHour = (currentHour + 1) % 24;
  const nextVideo = getVideoForHour(nextHour);

  const link = document.createElement('link');
  link.rel = 'prefetch';
  link.href = nextVideo.url;
  document.head.appendChild(link);
}

音乐缓存

javascript
// 使用 Service Worker 缓存音乐
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/music/')) {
    event.respondWith(
      caches.match(event.request).then((response) => {
        return response || fetch(event.request).then((response) => {
          return caches.open('music-cache').then((cache) => {
            cache.put(event.request, response.clone());
            return response;
          });
        });
      })
    );
  }
});

懒加载

javascript
// 懒加载音乐列表
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      loadMoreMusic();
    }
  });
});

observer.observe(document.getElementById('music-list-end'));

时间同步

背景视频根据现实时间自动切换:

时间视频
00:00 - 05:59深夜
06:00 - 11:59早晨
12:00 - 17:59下午
18:00 - 23:59傍晚
javascript
function getVideoForCurrentTime() {
  const hour = new Date().getHours();

  if (hour >= 0 && hour < 6) return 'night';
  if (hour >= 6 && hour < 12) return 'morning';
  if (hour >= 12 && hour < 18) return 'afternoon';
  return 'evening';
}

贡献

参考 CONTRIBUTING.md

许可证

MIT License - 详见 LICENSE

相关链接

Released under the MIT License.