从一个ACG爱好者的个人网站,到中国最具影响力的弹幕视频平台的诞生
本章将深入探讨B站的创立初期,从创始人徐逸的个人兴趣项目开始,到Bilibili品牌的确立,以及早期技术架构的搭建。这段历史不仅是一个网站的诞生史,更是中国互联网二次元文化崛起的见证。
徐逸,网名”⑨bishi”(源自东方Project中的琪露诺,因其被称为⑨而得名),1989年出生于浙江杭州。作为一个典型的85后,徐逸从小就对动漫和游戏充满热情。2007年,他考入北京邮电大学软件工程专业,正是这段求学经历为他日后创建B站奠定了技术基础。
家庭背景与成长环境: 徐逸出生在一个中产家庭,父母都是知识分子,这为他提供了相对宽松的成长环境。90年代末期,家里就购买了第一台电脑(赛扬300A处理器,32MB内存),让他很早就接触到了计算机和互联网。小学时期,他就开始自学HTML和简单的网页制作,初中时已经能够独立搭建个人主页。
早期编程经历:
在大学期间,徐逸展现出了对ACG(Animation、Comic、Game)文化的深厚兴趣和对技术的执着追求。他经常活跃在各类动漫论坛,同时也在不断提升自己的编程能力。作为一个技术宅,他精通PHP、JavaScript等Web开发技术,这些技能成为了他创建Mikufans的基础。
大学期间的技术积累:
┌─────────────────────────────────────────┐
│ 徐逸的技术栈演进 │
├─────────────────────────────────────────┤
│ 2007: HTML/CSS + PHP基础 │
│ ↓ │
│ 2008: LAMP全栈 + Ajax │
│ ↓ │
│ 2009: Flash/ActionScript + 视频处理 │
│ ↓ │
│ 2010: 服务器运维 + 数据库优化 │
└─────────────────────────────────────────┘
在北邮的学习期间,徐逸不仅在课堂上学习软件工程理论,更重要的是通过实践项目锻炼了自己的工程能力。他参与了多个开源项目,包括:
技术背景特点:
2007年6月,中国第一个弹幕视频网站AcFun(Anime Comic Fun)正式成立,它模仿日本的Niconico动画网站,将弹幕文化引入中国。AcFun的出现让徐逸看到了弹幕视频网站在中国的巨大潜力。
然而,AcFun在早期运营中存在诸多问题:
技术层面的问题:
运营层面的问题:
商业化困境:
徐逸作为AcFun的忠实用户(UID: 23456),深刻感受到了这些痛点。他在AcFun论坛上的技术建议帖子获得了大量用户支持,但始终没有得到官方回应。
2009年初的关键事件:
| 日期 | 事件 | 影响 |
|---|---|---|
| 2009.01.15 | AcFun遭遇DDoS攻击,瘫痪3天 | 日活跃用户从5万降至2万 |
| 2009.02.03 | 数据库崩溃,丢失一周数据 | 大量UP主流失 |
| 2009.03.20 | 域名到期未续费,停服12小时 | 用户信心严重受损 |
| 2009.04.10 | 视频服务器欠费被停 | 徐逸决定创建备用站 |
这些事件让徐逸萌生了创建一个”更稳定的弹幕视频网站”的想法。他在日记中写道:
“如果我来做,至少能保证网站不会三天两头挂掉。技术上并不难,关键是要有责任心和执行力。”—— 徐逸,2009年4月15日
┌────────────────────────────────────────┐
│ 2009年初的弹幕视频网站格局 │
├────────────────────────────────────────┤
│ │
│ 日本:Niconico(2006年成立) │
│ ├─ 用户数:1000万+ │
│ └─ 技术成熟,商业模式清晰 │
│ │
│ 中国:AcFun(2007年成立) │
│ ├─ 用户数:10万+ │
│ └─ 技术不稳定,运营困难 │
│ │
│ 机会:稳定的弹幕视频平台需求旺盛 │
└────────────────────────────────────────┘
2009年6月26日,徐逸正式上线了Mikufans.cn。这个日期的选择并非偶然——6月26日是初音未来在Niconico动画上第一个爆红视频《甩葱歌》的发布纪念日。网站名称来源于虚拟歌姬”初音未来”(Hatsune Miku),体现了他对二次元文化的热爱。
网站上线前的准备工作(2009年4月-6月):
4月15日:确定创建网站的决心
↓ (1周)
4月22日:完成技术选型和架构设计
↓ (2周)
5月6日:购买域名mikufans.cn(¥35/年)
↓ (1周)
5月13日:租用第一台VPS服务器
↓ (3周)
6月3日:完成核心功能开发
↓ (1周)
6月10日:内测,邀请10位朋友试用
↓ (2周)
6月24日:修复内测发现的问题
↓ (2天)
6月26日:正式上线!
作为一个个人网站,Mikufans的初始定位非常明确:为AcFun宕机时提供备用选择。
首页的第一版文案:
“当A站挂了的时候,你可以来这里看看。我会努力保证这里不会挂掉。 —— ⑨bishi”
上线首日数据:
初期网站特征:
早期用户增长数据:
| 时间 | 注册用户数 | 日活跃用户 | 视频数量 | 服务器成本 | 关键事件 |
|---|---|---|---|---|---|
| 2009.06 | 100+ | 20+ | 50+ | ¥300 | 网站上线 |
| 2009.07 | 500+ | 100+ | 200+ | ¥300 | AcFun推荐 |
| 2009.08 | 1000+ | 300+ | 500+ | ¥500 | 增加服务器 |
| 2009.09 | 2000+ | 500+ | 1000+ | ¥800 | 首个爆款视频 |
| 2009.10 | 3500+ | 800+ | 2000+ | ¥1200 | 社区初具规模 |
| 2009.11 | 5000+ | 1200+ | 3500+ | ¥1500 | 引入CDN |
| 2009.12 | 8000+ | 2000+ | 5000+ | ¥2000 | 首次盈亏平衡 |
用户来源分析(2009年7-9月):
┌────────────────────────────────────┐
│ 早期用户来源分布 │
├────────────────────────────────────┤
│ │
│ AcFun难民: ████████ 40% │
│ QQ群推广: ██████ 30% │
│ 贴吧引流: ████ 20% │
│ 朋友介绍: ██ 10% │
│ │
└────────────────────────────────────┘
关键转折点——第一个爆款视频(2009年9月): UP主”某幻君”上传的《【東方】Bad Apple!! 》PV获得了10万次播放,这是Mikufans历史上第一个真正意义上的爆款视频。这个视频的成功带来了:
徐逸在网站首页写道:”这是一个ACG相关的弹幕视频分享网站,大家可以在这里自由地发布、观看、吐槽各种有趣的视频。”这句简单的介绍,成为了B站最初的使命宣言。
早期运营策略:
2010年1月24日,一个看似普通的日子,却成为了B站历史上的重要转折点。这一天,Mikufans正式更名为Bilibili,域名从mikufans.cn变更为bilibili.us(后来才获得bilibili.com)。
更名的深层原因:
域名获取过程:
时间线:
2009.12 - 徐逸决定更换品牌名称
2010.01 - 注册bilibili.us域名(费用:¥68/年)
2010.01.24 - 正式启用新域名
2010.06 - 购得bilibili.tv域名(费用:¥500)
2011.06 - 最终获得bilibili.com(费用:约¥10万)
随着更名为Bilibili,网站的定位也发生了重要转变。徐逸意识到,要想做大做强,必须有清晰的品牌定位和发展方向。
B站早期的”三不”原则:
核心价值主张:
差异化竞争策略:
| 对比维度 | Bilibili | AcFun | 传统视频网站 |
|---|---|---|---|
| 内容来源 | UGC为主 | 搬运为主 | PGC为主 |
| 盈利模式 | 无广告 | 有广告 | 广告为主 |
| 社区氛围 | 严格管理 | 相对宽松 | 无社区概念 |
| 技术稳定性 | 高 | 低 | 高 |
| 用户门槛 | 答题机制 | 无门槛 | 无门槛 |
B站的成功很大程度上归功于其独特的社区文化。从Mikufans时期开始,徐逸就非常重视社区氛围的培养。
1. 会员答题机制(2010年5月推出)
为了保证社区质量,B站推出了独特的正式会员答题系统:
题目类型分布: | 类别 | 题目数量 | 难度等级 | 示例题目 | |——|———|———|———| | 动漫知识 | 30题 | ★★★ | “《新世纪福音战士》的导演是谁?” | | 游戏常识 | 20题 | ★★ | “马里奥第一次出现在哪款游戏中?” | | 弹幕礼仪 | 20题 | ★ | “以下哪种行为违反弹幕礼仪?” | | 网站规则 | 15题 | ★ | “B站禁止上传哪类内容?” | | 综合文化 | 15题 | ★★★★ | “Otaku一词的起源是?” |
答题通过率统计(2010年5-12月):
答题系统的技术实现:
// 早期答题系统的简化代码示例
class MemberExam {
private $questions = [];
private $pass_score = 60;
public function loadQuestions() {
// 从题库随机抽取100道题
$this->questions = $this->getRandomQuestions(100);
}
public function checkAnswers($user_answers) {
$score = 0;
foreach($user_answers as $qid => $answer) {
if($this->isCorrect($qid, $answer)) {
$score++;
}
}
return $score >= $this->pass_score;
}
}
2. 弹幕礼仪文化
B站建立了一套完整的弹幕礼仪规范:
3. UP主文化
B站将内容创作者称为”UP主”(源自upload),形成了独特的创作者生态:
| 时期 | UP主数量 | 代表人物 | 主要内容类型 |
|---|---|---|---|
| 2009年 | <100 | 个人爱好者 | 动画搬运 |
| 2010年 | 500+ | TSA、某幻君 | MAD创作、游戏实况 |
| 2011年 | 2000+ | 敖厂长、老E | 原创评论、教程 |
4. 特色活动与梗文化
┌─────────────────────────────────────┐
│ B站早期社区生态系统 │
├─────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ UP主 │ │
│ │ (创作者) │ │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 内容 │◄──── 弹幕互动 │
│ │ (视频) │ │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 用户 │ │
│ │ (观众) │ │
│ └──────────┘ │
│ │
│ 核心机制:答题→会员→参与→贡献 │
└─────────────────────────────────────┘
2009年,徐逸作为一个大学生,在技术选型上面临着多重考虑。最终选择LAMP(Linux + Apache + MySQL + PHP)架构,这个决定奠定了B站早期的技术基础。
选择PHP的原因:
技术栈对比(2009年的选择):
| 技术方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| PHP+MySQL | 开发快、成本低、易维护 | 性能瓶颈、并发限制 | 中小型网站 |
| Java+Oracle | 性能强、稳定性高 | 开发慢、成本高 | 大型企业应用 |
| Ruby on Rails | 开发效率高、约定优于配置 | 性能较差、托管贵 | 创业项目 |
| ASP.NET | 微软生态、工具完善 | Windows服务器贵 | 企业应用 |
B站早期的架构设计虽然简单,但体现了徐逸扎实的技术功底和前瞻性思维。
初始架构(2009年6月):
┌─────────────────────────────────────────────┐
│ 用户浏览器 │
└─────────────────┬───────────────────────────┘
│ HTTP/HTTPS
▼
┌─────────────────────────────────────────────┐
│ Nginx(反向代理) │
│ · 静态资源服务 │
│ · 负载均衡 │
│ · 防盗链 │
└─────────────────┬───────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Apache + PHP 5.3 │
│ · 业务逻辑处理 │
│ · 会话管理 │
│ · 模板渲染 │
└─────────────────┬───────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ MySQL 5.1 │
│ · 用户数据 │
│ · 视频元数据 │
│ · 弹幕数据 │
└─────────────────────────────────────────────┘
核心数据库设计:
-- 用户表
CREATE TABLE users (
uid INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE,
password VARCHAR(32), -- MD5加密
email VARCHAR(100),
reg_time TIMESTAMP,
last_login TIMESTAMP,
is_member TINYINT DEFAULT 0 -- 是否正式会员
);
-- 视频表
CREATE TABLE videos (
vid INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255),
description TEXT,
uploader_uid INT,
upload_time TIMESTAMP,
view_count INT DEFAULT 0,
file_path VARCHAR(255),
duration INT, -- 秒
status TINYINT -- 0:待审,1:通过,2:删除
);
-- 弹幕表(早期设计)
CREATE TABLE danmaku (
did INT PRIMARY KEY AUTO_INCREMENT,
vid INT,
uid INT,
content VARCHAR(255),
time_point FLOAT, -- 出现时间点
color VARCHAR(7), -- 颜色代码
size TINYINT, -- 字体大小
position TINYINT, -- 1:滚动,2:顶部,3:底部
send_time TIMESTAMP
);
文件存储策略:
尽管资源有限,徐逸still在性能优化上下了很大功夫,这些早期的优化经验为后续发展奠定了基础。
1. 数据库优化
-- 添加索引优化查询
ALTER TABLE videos ADD INDEX idx_upload_time (upload_time);
ALTER TABLE danmaku ADD INDEX idx_vid_time (vid, time_point);
-- 分表策略(2010年实施)
-- 弹幕表按视频ID哈希分10个表
CREATE TABLE danmaku_0 LIKE danmaku;
CREATE TABLE danmaku_1 LIKE danmaku;
-- ... danmaku_2 到 danmaku_9
2. PHP代码优化
// 使用Memcache缓存热点数据
class VideoCache {
private $memcache;
public function __construct() {
$this->memcache = new Memcache();
$this->memcache->connect('localhost', 11211);
}
public function getVideoInfo($vid) {
$key = "video_info_" . $vid;
$data = $this->memcache->get($key);
if ($data === false) {
// 缓存未命中,查询数据库
$data = $this->queryFromDB($vid);
// 缓存10分钟
$this->memcache->set($key, $data, 0, 600);
}
return $data;
}
}
// 使用OPcache加速PHP执行
// php.ini配置
// opcache.enable=1
// opcache.memory_consumption=128
3. 前端优化
// 弹幕渲染优化:使用对象池减少GC
var DanmakuPool = {
pool: [],
maxSize: 100,
created: 0,
inUse: 0,
get: function() {
this.inUse++;
if (this.pool.length > 0) {
return this.pool.pop();
}
this.created++;
return new Danmaku();
},
release: function(danmaku) {
this.inUse--;
if (this.pool.length < this.maxSize) {
danmaku.reset();
this.pool.push(danmaku);
}
},
// 统计信息
getStats: function() {
return {
created: this.created,
pooled: this.pool.length,
inUse: this.inUse,
efficiency: (1 - this.created / (this.created + this.pool.length)) * 100
};
}
};
// 图片懒加载优化版
var LazyLoader = {
threshold: 100, // 提前100px开始加载
loading: {},
init: function() {
this.loadImages();
window.addEventListener('scroll', this.throttle(this.loadImages.bind(this), 200));
},
loadImages: function() {
var images = document.querySelectorAll('img[data-src]');
images.forEach(function(img) {
if (this.isNearViewport(img) && !this.loading[img.dataset.src]) {
this.loading[img.dataset.src] = true;
this.loadImage(img);
}
}.bind(this));
},
isNearViewport: function(elem) {
var rect = elem.getBoundingClientRect();
return rect.top <= window.innerHeight + this.threshold &&
rect.bottom >= -this.threshold;
},
loadImage: function(img) {
var tempImg = new Image();
tempImg.onload = function() {
img.src = tempImg.src;
delete img.dataset.src;
delete this.loading[tempImg.src];
}.bind(this);
tempImg.src = img.dataset.src;
},
throttle: function(func, wait) {
var timeout;
return function() {
var context = this, args = arguments;
if (!timeout) {
timeout = setTimeout(function() {
timeout = null;
func.apply(context, args);
}, wait);
}
};
}
};
// 视频预加载策略
var VideoPreloader = {
queue: [],
loading: false,
add: function(videoUrl) {
this.queue.push(videoUrl);
if (!this.loading) {
this.processQueue();
}
},
processQueue: function() {
if (this.queue.length === 0) {
this.loading = false;
return;
}
this.loading = true;
var url = this.queue.shift();
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function() {
// 缓存到浏览器
if (xhr.status === 200) {
var blob = xhr.response;
var blobUrl = URL.createObjectURL(blob);
this.cacheVideo(url, blobUrl);
}
this.processQueue();
}.bind(this);
xhr.send();
},
cacheVideo: function(originalUrl, blobUrl) {
// 存储映射关系
window.videoCache = window.videoCache || {};
window.videoCache[originalUrl] = blobUrl;
}
};
性能优化效果(2009-2011):
| 指标 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 页面加载时间 | 3.5秒 | 1.2秒 | 65.7% |
| 并发用户数 | 100 | 500 | 400% |
| 数据库QPS | 50 | 300 | 500% |
| 带宽利用率 | 40% | 75% | 87.5% |
| 服务器成本 | ¥500/月 | ¥800/月 | 60%(相对性能提升) |
架构演进时间线:
2009.06 ━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2011.12
┃
┣━ 2009.06: 单机架构上线
┃
┣━ 2009.10: 引入Memcache缓存
┃
┣━ 2010.02: 数据库读写分离
┃
┣━ 2010.06: 静态资源CDN化
┃
┣━ 2010.11: 视频转码服务独立
┃
┣━ 2011.03: 引入消息队列
┃
┗━ 2011.09: 开始微服务化探索
弹幕(Danmaku/Danmu),这个源自日本的概念,成为了B站最具标志性的特征。徐逸在创建Mikufans时,就将弹幕系统作为核心功能来实现。
弹幕的起源与发展:
B站弹幕系统的设计理念:
┌──────────────────────────────────────────┐
│ 弹幕系统核心理念 │
├──────────────────────────────────────────┤
│ │
│ 1. 共时性体验 │
│ └─ 不同时间的用户仿佛同时观看 │
│ │
│ 2. 情感共鸣 │
│ └─ 即时分享观看感受 │
│ │
│ 3. 二次创作 │
│ └─ 弹幕本身成为内容的一部分 │
│ │
│ 4. 社交属性 │
│ └─ 陌生人之间的默契互动 │
└──────────────────────────────────────────┘
B站早期的弹幕系统实现,虽然简单但巧妙,为后续的技术迭代打下了良好基础。
1. 数据存储方案
最初,B站采用XML文件存储弹幕数据:
<!-- /danmaku/1.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<i>
<chatserver>chat.bilibili.com</chatserver>
<chatid>1</chatid>
<mission>0</mission>
<maxlimit>1000</maxlimit>
<d p="10.5,1,25,16777215,1312345678,0,aaabbb,12345">
前方高能预警!
</d>
<d p="15.2,1,25,16711680,1312345679,0,cccddd,12346">
233333
</d>
</i>
<!-- p参数说明:
时间,类型,字号,颜色,时间戳,弹幕池,用户hash,弹幕id
-->
后期迁移到数据库存储(2010年):
// 弹幕加载类
class DanmakuLoader {
private $db;
private $cache;
public function loadByVideo($vid, $segment = 1) {
// 分段加载策略,每段加载1000条
$offset = ($segment - 1) * 1000;
$sql = "SELECT * FROM danmaku
WHERE vid = ?
ORDER BY time_point
LIMIT 1000 OFFSET ?";
$result = $this->db->query($sql, [$vid, $offset]);
return $this->formatToXML($result);
}
private function formatToXML($danmakus) {
$xml = '<?xml version="1.0" encoding="UTF-8"?><i>';
foreach ($danmakus as $d) {
$p = sprintf("%f,%d,%d,%d,%d,0,%s,%d",
$d['time_point'],
$d['type'],
$d['size'],
$d['color'],
$d['timestamp'],
$d['user_hash'],
$d['did']
);
$xml .= sprintf('<d p="%s">%s</d>',
$p, htmlspecialchars($d['content']));
}
$xml .= '</i>';
return $xml;
}
}
2. 前端渲染实现
早期使用Flash ActionScript实现弹幕渲染:
// ActionScript 3.0 弹幕渲染核心
package {
public class DanmakuEngine {
private var stage:Stage;
private var danmakuList:Array = [];
private var timer:Timer;
public function DanmakuEngine(stage:Stage) {
this.stage = stage;
this.timer = new Timer(50); // 20fps
this.timer.addEventListener(TimerEvent.TIMER, render);
}
public function addDanmaku(text:String, time:Number,
color:uint, size:int):void {
var danmaku:Object = {
text: text,
time: time,
color: color,
size: size,
x: stage.stageWidth,
y: Math.random() * stage.stageHeight
};
danmakuList.push(danmaku);
}
private function render(e:TimerEvent):void {
var currentTime:Number = getVideoTime();
for each (var d:Object in danmakuList) {
if (Math.abs(d.time - currentTime) < 0.1) {
showDanmaku(d);
}
}
updatePositions();
}
private function showDanmaku(d:Object):void {
var tf:TextField = new TextField();
tf.text = d.text;
tf.textColor = d.color;
tf.size = d.size;
tf.x = d.x;
tf.y = d.y;
stage.addChild(tf);
}
}
}
后期HTML5 Canvas实现(2011年开始探索):
// Canvas弹幕引擎 - 优化版本
class DanmakuEngine {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.danmakus = [];
this.lastTime = 0;
this.lanes = this.initLanes(); // 弹道系统
this.collisionMap = new Map(); // 碰撞检测
}
initLanes() {
const laneHeight = 30;
const laneCount = Math.floor(this.canvas.height / laneHeight);
return Array(laneCount).fill(null).map(() => ({
occupied: false,
releaseTime: 0
}));
}
add(text, time, color, size, type) {
const lane = this.findAvailableLane();
this.danmakus.push({
text: text,
time: time * 1000, // 转换为毫秒
color: color || '#FFFFFF',
size: size || 25,
type: type || 1, // 1:滚动 2:顶部 3:底部
x: this.canvas.width,
y: lane * 30 + 20, // 基于弹道计算Y坐标
lane: lane,
speed: 2 + Math.random(),
width: this.measureText(text, size),
opacity: 1,
show: false,
shadow: true // 添加阴影效果
});
}
findAvailableLane() {
const now = Date.now();
for (let i = 0; i < this.lanes.length; i++) {
if (!this.lanes[i].occupied || this.lanes[i].releaseTime < now) {
this.lanes[i].occupied = true;
this.lanes[i].releaseTime = now + 3000; // 3秒后释放
return i;
}
}
return Math.floor(Math.random() * this.lanes.length);
}
measureText(text, size) {
this.ctx.font = `${size}px Arial`;
return this.ctx.measureText(text).width;
}
update(currentTime) {
// 使用双缓冲技术
const offscreen = document.createElement('canvas');
offscreen.width = this.canvas.width;
offscreen.height = this.canvas.height;
const offCtx = offscreen.getContext('2d');
// 批量渲染相同样式的弹幕
const danmakuByStyle = new Map();
this.danmakus.forEach(d => {
if (Math.abs(d.time - currentTime) < 100 && !d.show) {
d.show = true;
}
if (d.show) {
// 更新位置
if (d.type === 1) { // 滚动弹幕
d.x -= d.speed;
}
// 按样式分组
const styleKey = `${d.size}-${d.color}`;
if (!danmakuByStyle.has(styleKey)) {
danmakuByStyle.set(styleKey, []);
}
danmakuByStyle.get(styleKey).push(d);
// 移除屏幕外的弹幕
if (d.x < -d.width) {
d.show = false;
this.lanes[d.lane].occupied = false;
}
}
});
// 批量绘制
danmakuByStyle.forEach((danmakus, style) => {
const [size, color] = style.split('-');
offCtx.font = `bold ${size}px Arial`;
offCtx.fillStyle = color;
// 添加文字阴影
offCtx.shadowColor = 'rgba(0, 0, 0, 0.5)';
offCtx.shadowBlur = 2;
offCtx.shadowOffsetX = 1;
offCtx.shadowOffsetY = 1;
danmakus.forEach(d => {
offCtx.fillText(d.text, d.x, d.y);
});
});
// 将离屏画布内容复制到主画布
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(offscreen, 0, 0);
requestAnimationFrame(() => this.update(currentTime + 50));
}
// 性能监控
getPerformanceStats() {
return {
totalDanmakus: this.danmakus.length,
activeDanmakus: this.danmakus.filter(d => d.show).length,
fps: this.calculateFPS(),
memoryUsage: this.estimateMemoryUsage()
};
}
calculateFPS() {
const now = Date.now();
const fps = 1000 / (now - this.lastTime);
this.lastTime = now;
return Math.round(fps);
}
estimateMemoryUsage() {
// 估算内存使用(KB)
const danmakuSize = 200; // 每个弹幕对象约200字节
return Math.round(this.danmakus.length * danmakuSize / 1024);
}
}
B站的弹幕不仅是技术创新,更形成了独特的文化现象。
弹幕礼仪的形成:
| 弹幕用语 | 含义 | 使用场景 |
|---|---|---|
| 前方高能 | 精彩内容预警 | 战斗、高潮场景前 |
| 空降指挥部 | 时间定位 | 指引精彩片段位置 |
| 泪目 | 感动流泪 | 感人场景 |
| 233 | 大笑 | 搞笑内容 |
| 硬币已投 | 支持UP主 | 优质内容认可 |
| 护体 | 保护弹幕 | 恐怖内容时互相鼓励 |
弹幕密度控制算法:
# 弹幕密度控制伪代码
class DanmakuFilter:
def __init__(self):
self.max_density = 50 # 屏幕最大弹幕数
self.similarity_threshold = 0.8 # 相似度阈值
def filter(self, danmakus, current_time):
# 1. 时间窗口内的弹幕
window_danmakus = [d for d in danmakus
if abs(d.time - current_time) < 1]
# 2. 相似度过滤(去重)
filtered = []
for d in window_danmakus:
if not self.is_similar(d, filtered):
filtered.append(d)
# 3. 密度控制
if len(filtered) > self.max_density:
# 随机采样或按质量排序
filtered = self.sample_by_quality(filtered)
return filtered
def is_similar(self, d1, danmaku_list):
for d2 in danmaku_list:
if self.string_similarity(d1.text, d2.text) > self.similarity_threshold:
return True
return False
弹幕数据统计(2009-2011):
┌──────────────────────────────────────────────┐
│ 弹幕系统增长数据 │
├──────────────────────────────────────────────┤
│ │
│ 2009年:日均弹幕 1000+ │
│ │ │
│ │ ████ │
│ │ │
│ 2010年:日均弹幕 10000+ │
│ │ │
│ │ ████████████ │
│ │ │
│ 2011年:日均弹幕 100000+ │
│ │ │
│ │ ████████████████████████ │
│ │
│ 增长率:每年10倍 │
└──────────────────────────────────────────────┘
2009年到2011年,是B站从个人网站成长为知名弹幕视频平台的关键三年。在这段时期内:
这个阶段的B站,虽然规模不大,但已经展现出了与众不同的发展潜力。徐逸的技术理想主义和对用户体验的执着追求,为B站日后的腾飞奠定了坚实基础。
2009.06.26 Mikufans.cn正式上线
2009.10 引入Memcache缓存系统
2010.01.24 更名为Bilibili
2010.05 推出会员答题系统
2010.06 获得bilibili.tv域名
2011.03 用户突破10万
2011.06 获得bilibili.com域名
2011.09 开始探索微服务架构
2011.12 月活跃用户达到50万
下一章预告:《成长期(2012-2014)》将讲述陈睿的加入如何改变B站的命运,以及技术团队如何从个人作坊走向正规军。