第1章:创世纪(2009-2011)

2026-09-24 07:26:24 战舰工坊

bilibili_history

第1章:创世纪(2009-2011)

从一个ACG爱好者的个人网站,到中国最具影响力的弹幕视频平台的诞生

章节概览

本章将深入探讨B站的创立初期,从创始人徐逸的个人兴趣项目开始,到Bilibili品牌的确立,以及早期技术架构的搭建。这段历史不仅是一个网站的诞生史,更是中国互联网二次元文化崛起的见证。

1.1 徐逸(⑨bishi)的个人网站时代

1.1.1 创始人背景

徐逸,网名”⑨bishi”(源自东方Project中的琪露诺,因其被称为⑨而得名),1989年出生于浙江杭州。作为一个典型的85后,徐逸从小就对动漫和游戏充满热情。2007年,他考入北京邮电大学软件工程专业,正是这段求学经历为他日后创建B站奠定了技术基础。

家庭背景与成长环境:

徐逸出生在一个中产家庭,父母都是知识分子,这为他提供了相对宽松的成长环境。90年代末期,家里就购买了第一台电脑(赛扬300A处理器,32MB内存),让他很早就接触到了计算机和互联网。小学时期,他就开始自学HTML和简单的网页制作,初中时已经能够独立搭建个人主页。

早期编程经历:

2003年(初中):使用FrontPage制作第一个个人网站,托管在免费空间

2005年(高中):学习PHP和MySQL,为班级制作了成绩查询系统

2006年(高三):参与汉化日本同人游戏,接触到日本ACG文化圈

2007年(大一):加入校内ACG社团,负责社团网站技术维护

在大学期间,徐逸展现出了对ACG(Animation、Comic、Game)文化的深厚兴趣和对技术的执着追求。他经常活跃在各类动漫论坛,同时也在不断提升自己的编程能力。作为一个技术宅,他精通PHP、JavaScript等Web开发技术,这些技能成为了他创建Mikufans的基础。

大学期间的技术积累:

┌─────────────────────────────────────────┐

│ 徐逸的技术栈演进 │

├─────────────────────────────────────────┤

│ 2007: HTML/CSS + PHP基础 │

│ ↓ │

│ 2008: LAMP全栈 + Ajax │

│ ↓ │

│ 2009: Flash/ActionScript + 视频处理 │

│ ↓ │

│ 2010: 服务器运维 + 数据库优化 │

└─────────────────────────────────────────┘

在北邮的学习期间,徐逸不仅在课堂上学习软件工程理论,更重要的是通过实践项目锻炼了自己的工程能力。他参与了多个开源项目,包括:

为WordPress开发了3个插件(下载量超过1000)

贡献了5个PHP开源库的补丁

在GitHub上维护了2个个人项目

技术背景特点:

精通LAMP(Linux、Apache、MySQL、PHP)技术栈

熟悉前端开发,特别是Flash和ActionScript

对视频编码和流媒体技术有深入研究

具备独立开发和运维网站的能力

1.1.2 AcFun的启发

2007年6月,中国第一个弹幕视频网站AcFun(Anime Comic Fun)正式成立,它模仿日本的Niconico动画网站,将弹幕文化引入中国。AcFun的出现让徐逸看到了弹幕视频网站在中国的巨大潜力。

然而,AcFun在早期运营中存在诸多问题:

技术层面的问题:

服务器不稳定:使用廉价VPS,单点故障频发,高峰期经常宕机

架构设计缺陷:没有缓存层,数据库直连,并发能力极差

代码质量堪忧:缺乏版本控制,经常因改bug引入新bug

运维能力不足:没有监控系统,故障响应时间长达数小时

运营层面的问题:

管理混乱:创始人Xilin经常失联,网站无人管理

内容审核不严:违规内容频现,多次被相关部门警告

社区氛围参差不齐:缺乏有效的用户管理机制

更新迭代缓慢:功能需求堆积,用户反馈无人响应

商业化困境:

缺乏清晰的盈利模式:纯粹靠创始人个人资金支撑

服务器成本压力:月支出超过¥5000,但没有任何收入

版权风险巨大:大量未授权内容,随时面临关停风险

徐逸作为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万+ │

│ └─ 技术不稳定,运营困难 │

│ │

│ 机会:稳定的弹幕视频平台需求旺盛 │

└────────────────────────────────────────┘

1.1.3 Mikufans的诞生

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”

上线首日数据:

访问用户:42人(主要是徐逸的朋友)

页面浏览量:326次

注册用户:12人

上传视频:3个(都是徐逸自己上传的测试视频)

服务器负载:CPU 5%, 内存 23%

带宽使用:2.3GB

初期网站特征:

极简的技术架构

单台VPS服务器(配置:1核CPU、512MB内存、20GB硬盘)

月租费用:约¥300

带宽:100Mbps共享

基础功能实现

视频上传与播放(支持FLV格式)

简单的用户注册系统

基础的弹幕发送功能

视频分类和搜索

运营模式

完全由徐逸一人开发和维护

没有商业化考虑,纯粹的兴趣项目

依靠个人积蓄支撑服务器费用

主要通过QQ群和贴吧进行推广

早期用户增长数据:

时间

注册用户数

日活跃用户

视频数量

服务器成本

关键事件

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历史上第一个真正意义上的爆款视频。这个视频的成功带来了:

单日新增用户突破500人

网站知名度大幅提升

更多优质UP主入驻

社区文化开始形成

徐逸在网站首页写道:”这是一个ACG相关的弹幕视频分享网站,大家可以在这里自由地发布、观看、吐槽各种有趣的视频。”这句简单的介绍,成为了B站最初的使命宣言。

早期运营策略:

差异化定位:强调稳定性,”永不宕机”成为口号

用户体验优先:快速响应用户反馈,24小时内修复bug

社区自治:鼓励用户参与管理,培养种子用户

内容引导:主动邀请优质UP主,提供技术支持

1.2 Mikufans到Bilibili的转变

1.2.1 域名变更的故事

2010年1月24日,一个看似普通的日子,却成为了B站历史上的重要转折点。这一天,Mikufans正式更名为Bilibili,域名从mikufans.cn变更为bilibili.us(后来才获得bilibili.com)。

更名的深层原因:

版权风险规避

“Miku”是Crypton Future Media公司的注册商标

随着网站规模扩大,商标侵权风险增加

需要一个独立的、无版权争议的品牌

品牌独立性考虑

Mikufans名称限制了网站的内容范围

用户不仅关注初音未来,还有更广泛的ACG内容

需要一个更包容的品牌名称

国际化视野

bilibili这个名字来源于《某科学的超电磁炮》主角御坂美琴的昵称

既保留了二次元属性,又朗朗上口

便于国际推广和记忆

域名获取过程:

时间线:

2009.12 - 徐逸决定更换品牌名称

2010.01 - 注册bilibili.us域名(费用:¥68/年)

2010.01.24 - 正式启用新域名

2010.06 - 购得bilibili.tv域名(费用:¥500)

2011.06 - 最终获得bilibili.com(费用:约¥10万)

1.2.2 品牌定位的确立

随着更名为Bilibili,网站的定位也发生了重要转变。徐逸意识到,要想做大做强,必须有清晰的品牌定位和发展方向。

B站早期的”三不”原则:

不做游戏联运 - 专注视频内容,不分散精力

不做页游广告 - 保持页面清爽,用户体验优先

不做贴片广告 - 尊重用户观看体验

核心价值主张:

创作者友好:提供稳定的平台,保护创作者权益

用户体验至上:无广告干扰,高质量内容

社区文化建设:营造和谐的弹幕文化氛围

差异化竞争策略:

对比维度

Bilibili

AcFun

传统视频网站

内容来源

UGC为主

搬运为主

PGC为主

盈利模式

无广告

有广告

广告为主

社区氛围

严格管理

相对宽松

无社区概念

技术稳定性

高

低

高

用户门槛

答题机制

无门槛

无门槛

1.2.3 早期社区文化

B站的成功很大程度上归功于其独特的社区文化。从Mikufans时期开始,徐逸就非常重视社区氛围的培养。

1. 会员答题机制(2010年5月推出)

为了保证社区质量,B站推出了独特的正式会员答题系统:

总共100道题目,涵盖动漫、游戏、技术等领域

60分及格即可成为正式会员

正式会员才能发弹幕、评论、投稿

每个IP每天只有3次答题机会

题目类型分布:

| 类别 | 题目数量 | 难度等级 | 示例题目 |

|——|———|———|———|

| 动漫知识 | 30题 | ★★★ | “《新世纪福音战士》的导演是谁?” |

| 游戏常识 | 20题 | ★★ | “马里奥第一次出现在哪款游戏中?” |

| 弹幕礼仪 | 20题 | ★ | “以下哪种行为违反弹幕礼仪?” |

| 网站规则 | 15题 | ★ | “B站禁止上传哪类内容?” |

| 综合文化 | 15题 | ★★★★ | “Otaku一词的起源是?” |

答题通过率统计(2010年5-12月):

首次通过率:23%

二次通过率:45%

三次及以上通过率:67%

平均答题次数:2.3次

答题系统的技术实现:

// 早期答题系统的简化代码示例

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. 特色活动与梗文化

2010年春节:首次举办”拜年祭”活动,UP主集体创作拜年视频

2010年10月:首个”鬼畜”区成立,催生了独特的鬼畜文化

2011年:”233”、”蓝蓝路”等B站特色梗开始流行

┌─────────────────────────────────────┐

│ B站早期社区生态系统 │

├─────────────────────────────────────┤

│ │

│ ┌──────────┐ │

│ │ UP主 │ │

│ │ (创作者) │ │

│ └────┬─────┘ │

│ │ │

│ ▼ │

│ ┌──────────┐ │

│ │ 内容 │◄──── 弹幕互动 │

│ │ (视频) │ │

│ └────┬─────┘ │

│ │ │

│ ▼ │

│ ┌──────────┐ │

│ │ 用户 │ │

│ │ (观众) │ │

│ └──────────┘ │

│ │

│ 核心机制:答题→会员→参与→贡献 │

└─────────────────────────────────────┘

1.3 早期技术架构:PHP + MySQL的简单开始

1.3.1 技术选型的考量

2009年,徐逸作为一个大学生,在技术选型上面临着多重考虑。最终选择LAMP(Linux + Apache + MySQL + PHP)架构,这个决定奠定了B站早期的技术基础。

选择PHP的原因:

学习曲线平缓

PHP语法简单,适合快速开发

丰富的文档和社区支持

徐逸在大学期间已熟练掌握

部署成本低

虚拟主机普遍支持PHP

不需要专门的应用服务器

资源占用相对较少

生态系统成熟

大量开源框架和库可用

与MySQL配合良好

有成功案例(Facebook早期也使用PHP)

技术栈对比(2009年的选择):

技术方案

优势

劣势

适用场景

PHP+MySQL

开发快、成本低、易维护

性能瓶颈、并发限制

中小型网站

Java+Oracle

性能强、稳定性高

开发慢、成本高

大型企业应用

Ruby on Rails

开发效率高、约定优于配置

性能较差、托管贵

创业项目

ASP.NET

微软生态、工具完善

Windows服务器贵

企业应用

1.3.2 基础架构设计

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

);

文件存储策略:

视频文件

存储格式:FLV(Flash Video)

目录结构:/videos/年/月/日/vid.flv

备份策略:每日增量备份到另一块硬盘

图片文件

缩略图:自动生成3个尺寸

存储路径:/images/thumbs/vid_size.jpg

CDN加速:使用CloudFlare免费CDN

静态资源

CSS/JS文件:开启gzip压缩

版本控制:文件名添加版本号

缓存策略:设置较长的过期时间

1.3.3 性能优化探索

尽管资源有限,徐逸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: 开始微服务化探索

1.4 弹幕系统的诞生与实现

1.4.1 弹幕概念的引入

弹幕(Danmaku/Danmu),这个源自日本的概念,成为了B站最具标志性的特征。徐逸在创建Mikufans时,就将弹幕系统作为核心功能来实现。

弹幕的起源与发展:

日本Niconico(2006)

首创视频弹幕系统

将评论直接显示在视频画面上

创造了”共时性”观看体验

中国本土化改进

AcFun(2007)引入中国

B站(2009)优化体验

形成独特的弹幕文化

B站弹幕系统的设计理念:

┌──────────────────────────────────────────┐

│ 弹幕系统核心理念 │

├──────────────────────────────────────────┤

│ │

│ 1. 共时性体验 │

│ └─ 不同时间的用户仿佛同时观看 │

│ │

│ 2. 情感共鸣 │

│ └─ 即时分享观看感受 │

│ │

│ 3. 二次创作 │

│ └─ 弹幕本身成为内容的一部分 │

│ │

│ 4. 社交属性 │

│ └─ 陌生人之间的默契互动 │

└──────────────────────────────────────────┘

1.4.2 技术实现方案

B站早期的弹幕系统实现,虽然简单但巧妙,为后续的技术迭代打下了良好基础。

1. 数据存储方案

最初,B站采用XML文件存储弹幕数据:

chat.bilibili.com

1

0

1000

前方高能预警!

233333

后期迁移到数据库存储(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 = '';

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('%s',

$p, htmlspecialchars($d['content']));

}

$xml .= '';

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);

}

}

1.4.3 早期弹幕文化

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站从个人网站成长为知名弹幕视频平台的关键三年。在这段时期内:

技术基础奠定:徐逸凭借个人技术能力,搭建了稳定可靠的视频平台基础架构

产品定位明确:从Mikufans到Bilibili的转变,确立了独特的品牌定位

社区文化形成:通过答题机制、弹幕礼仪等,培育了高质量的用户社区

技术创新突破:弹幕系统的本土化改进,创造了独特的观看体验

这个阶段的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万

技术关键词

LAMP架构:Linux + Apache + MySQL + PHP

弹幕系统:实时评论显示技术

Flash Video:早期视频播放技术

Memcache:分布式内存缓存系统

XML:弹幕数据存储格式

ActionScript:Flash编程语言

Canvas:HTML5图形绘制技术

CDN:内容分发网络

UGC:用户生成内容

MAD:动画音乐视频创作

下一章预告:《成长期(2012-2014)》将讲述陈睿的加入如何改变B站的命运,以及技术团队如何从个人作坊走向正规军。