Skip to content

Commit 041a223

Browse files
committed
Better API Support and 2-way communication
1 parent 44893fb commit 041a223

File tree

12 files changed

+1087
-99
lines changed

12 files changed

+1087
-99
lines changed
Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,45 @@
1-
CommonFormat JSON标准化弹幕定义
1+
CCLNative CCL原生弹幕格式 JSON
22
===================
3-
以下标为“[OPT]” 为可选字段,剩下的为必选字段。
3+
CCLNative 是一套原生于 CCL的弹幕格式,用于配合 WordPress插件等现代化的HTML5弹幕插件。设计的
4+
主要思想是方便 JS 读取和未来的可扩展性。
5+
6+
根结构
7+
-------------------
8+
标注 [OPT] 的字段为可选字段
49

510
{
6-
"text": <String>,
7-
"stime": <uint>,
8-
"type": <String>,
9-
"color": <String. Format in HTML hexcode "RRGGBB">,
10-
"id": <uint Database ID>,
11-
"ctime": <Timestamp. Creation Date>,
12-
[OPT] "params":{
13-
14-
}
11+
"v":1,
12+
[OPT] "len": 10,
13+
[OPT] "offset": 0,
14+
[OPT] "socket": "",
15+
[OPT] "alias":{...},
16+
"timeline":[
17+
...
18+
]
1519
}
20+
21+
1. `v` - Version Number
22+
版本字段是负责告知解析器目前的版本号码的。所有的CCLNative实现需要遵循如下原则:对于版本1必须
23+
提供完全的支持,对于更高级的版本,应该尽可能提供支持。同时版本之间必须保证字段互通性,即,如果
24+
有字段 A 出现在版本 V 下,则任何大于版本 V 的版本,都应该无歧义的依然同样的解析 A 。
25+
26+
2. `len` - Length
27+
弹幕总数。如果 len 与 timeline 等长,则说明本文件包括所有弹幕,否则如果 len 大于
28+
timeline长度说明本文件只包含弹幕库的部分弹幕。这种场合下远程服务有可能提供方法获取所有弹幕,
29+
剩余弹幕,也有可能不提供此方法(用 len 表示历史弹幕总数)。len不得小于 timeline 长度。
30+
31+
3. `socket` - Socket id
32+
socket接口初始化配置字段,用于建立与聊天服务器的链接。
33+
34+
4. `alias` - Danmaku object property field alias
35+
这个字段是一个一层对应表,表示了弹幕文件的属性字段和标准字段的定义。比如如果定义为 "m":"mode"
36+
则在解析时 timeline 中的所有弹幕对象的 "m" 属性会被理解为 "mode"。当然,如果原来存在
37+
"mode" 则 "mode"属性还是会被理解为 "mode",在二者不同的情况下,哪个属性覆盖哪个是不确定的。
38+
39+
5. `timeline` - Danmaku Object timeline
40+
时间轴。包含精华。
41+
42+
timeline结构
43+
----------------
44+
timeline里的弹幕对象没有时序保证,一些实现可能会把最大的弹幕放在前面来保证加载,也有实现可能会把
45+
大的弹幕放在后方,而先行加载大量小型弹幕。

docs/scripting/OOAPI.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ OOAPI (Out-Of API) API规制外函数
22
=====================
33

44
OOAPI函数是不参考 Bilibili高级脚本弹幕的部分。其中包括如下函数,我们强烈不建议对OOAPI函数进行
5-
任何覆盖或改写。
5+
任何覆盖或改写。OOAPI提供了一套底层的相对安全的信息交流体系。
66

77
__trace(object:Object, traceMode:String)
88
---------------------
@@ -11,10 +11,11 @@ __trace(object:Object, traceMode:String)
1111
- 'warn' : Warning 警告,告知API使用错误或Deprecate通知,关闭严格模式则不输出
1212
- 'err' : Error 错误,永远输出。一般来说 err 表示无法纠正的错误,上一个操作可能已经失效。
1313
- 'fatal' : Fatal 引擎错误,永远输出。一般来说一旦 trace 出 fatal错误,BScript可以终止目前的Worker实例
14+
trace会发送到空channel,也因此无法绑定 "" 作为 channel 名称
1415

1516
__channel(id:String, payload:Object, callback:Function)
1617
---------------------
17-
底层双向通讯接口。向 id 发送 payload。如果 Channel没有定义回调则 callback 不会被执行
18+
底层双向通讯接口。向 id 发送 payload。如果 Channel 没有定义回调则 callback 不会被执行
1819
否则 callback 会返回回调信息。
1920

2021
__schannel(id:String, callback:Function)

src/scripting/Host.js

Lines changed: 207 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
var CCLScripting = function(workerUrl){
22
this.version = 1.0;
33
this.workerUrl = workerUrl;
4+
this.logger = new function(){
5+
this.log = function(m){
6+
console.log(m);
7+
};
8+
this.error = function(m){
9+
console.error(m);
10+
};
11+
this.warn = function(m){
12+
console.warn(m);
13+
};
14+
};
415
this.getWorker = function(){
516
return new Worker(this.workerUrl);
617
};
7-
this.getScriptingContext(stage){
8-
return new this.ScriptingContext(stage);
18+
this.getScriptingContext = function(stage){
19+
return new this.ScriptingContext(this, stage);
20+
};
21+
this.getSandbox = function(stage, player){
22+
return new this.BridgedSandbox(this, stage, player);
923
};
1024
};
1125

@@ -15,27 +29,78 @@ var CCLScripting = function(workerUrl){
1529
return;
1630
}
1731

18-
CCLScripting.prototype.ScriptingContext = function(stage){
32+
CCLScripting.prototype.ScriptingContext = function(scripter, stage){
1933
// Here in the Scripting Context we also have a objects
34+
var objects = {};
35+
this.registerObject = function(objectId, serialized){
36+
if(typeof this.Unpack[serialized["class"]] === "function"){
37+
objects[objectId] = new this.Unpack[serialized["class"]](stage,
38+
serialized);
39+
}else{
40+
scripter.logger.error("Cannot unpack class \"" +
41+
serialized["class"] + "\". No valid unpacker found");
42+
return;
43+
}
44+
};
2045

21-
};
22-
23-
CCLScripting.prototype.BridgedSandbox = function(stage){
24-
var worker = this.getWorker();
25-
var context = this.getScriptingContext(stage);
26-
var channels = {
27-
"::worker:state":{
28-
"max":0,
29-
"listeners":[]
46+
this.deregisterObject = function(objectId){
47+
delete objects[objectId];
48+
};
49+
50+
this.callMethod = function(objectId, methodName, params){
51+
if(!objects[objectId]){
52+
scripter.logger.error("Object not found.");
53+
return;
54+
}
55+
if(!objects[objectId][methodName]){
56+
scripter.logger.error("Method \"" + methodName
57+
+ "\" not defined for object of type " +
58+
objects[objectId].getClass() +".");
59+
return;
3060
}
61+
try{
62+
objects[objectId][methodName](params);
63+
}catch(e){
64+
if(e.stack){
65+
scripter.logger.error(e.stack);
66+
}else{
67+
scripter.logger.error(e.toString());
68+
};
69+
}
70+
};
71+
72+
this.clear = function(){
73+
3174
};
75+
};
76+
77+
CCLScripting.prototype.ScriptingContext.prototype.Unpack = {};
78+
79+
CCLScripting.prototype.BridgedSandbox = function(scripter, stage, player){
80+
var worker = scripter.getWorker();
81+
var context = scripter.getScriptingContext(stage);
82+
var playerAbst = player;
83+
var channels = {};
3284
var isRunning = false;
85+
var sandbox = this;
3386

3487
if(!worker){
3588
throw new Error("SANDBOX: Worker pool exhausted.");
3689
}
3790

38-
var addListener = function(channel, listener){
91+
this.getLogger = function(){
92+
return scripter.logger;
93+
};
94+
95+
this.getPlayer = function(){
96+
return playerAbst;
97+
};
98+
99+
this.getContext = function(){
100+
return context;
101+
};
102+
103+
this.addListener = function(channel, listener){
39104
if(!channels[channel]){
40105
channels[channel] = {
41106
"max":0,
@@ -57,40 +122,160 @@ var CCLScripting = function(workerUrl){
57122
try{
58123
channels[msg.channel].listeners[i](msg.payload);
59124
}catch(e){
60-
__trace(e, 'err');
125+
scripter.logger.error(e);
61126
}
62127
}
63128
}else{
64-
console.log("Message for channel:" + msg.channel +
65-
" but channel not existant.");
129+
scripter.logger.warn("Message for channel \"" + msg.channel +
130+
"\" but channel not existant.");
66131
}
67132
};
68133

69-
worker.onmessage = function(event){
134+
var WorkerHook = function(event){
70135
try{
71136
var resp = JSON.parse(event.data);
72137
}catch(e){
73138
console.log(e);
74139
return;
75140
}
76-
if(!isRunning){
77-
if(resp.channel === "::worker:state"){
78-
if(resp.payload === "running" && resp.auth === "worker"){
79-
isRunning = true;
141+
if(resp.channel === ""){
142+
switch(resp.mode){
143+
case "log":
144+
default:{
145+
scripter.logger.log(resp.obj);
146+
break;
80147
}
81-
}
148+
case "warn":{
149+
scripter.logger.warn(resp.obj);
150+
break;
151+
}
152+
case "err":{
153+
scripter.logger.error(resp.obj);
154+
break;
155+
}
156+
case "fatal":{
157+
scripter.logger.error(resp.obj);
158+
sandbox.resetWorker();
159+
return;
160+
}
161+
};
82162
return;
163+
}
164+
if(resp.channel.substring(0,8) === "::worker"){
165+
var RN = resp.channel.substring(8);
166+
switch(RN){
167+
case ":state":{
168+
if(resp.payload === "running" && resp.auth === "worker"){
169+
isRunning = true;
170+
channels = {};
171+
sandbox.init();
172+
}
173+
break;
174+
}
175+
default:{
176+
console.log(resp);
177+
break;
178+
}
179+
}
83180
}else{
84181
dispatchMessage(resp);
85182
}
86183
};
87184

185+
this.resetWorker = function(){
186+
try{
187+
worker.terminate();
188+
}catch(e){}
189+
worker = scripter.getWorker();
190+
if(!worker){
191+
throw new Error("SANDBOX: Worker pool exhausted.");
192+
}
193+
worker.addEventListener("message", WorkerHook);
194+
};
195+
196+
worker.addEventListener("message", WorkerHook);
197+
88198
this.eval = function(code){
89199
// Pushes the code to be evaluated on the Worker
200+
if(!isRunning){
201+
throw new Error("Worker offline");
202+
}
90203
worker.postMessage(JSON.stringify({
91204
"channel":"::eval",
92205
"payload":code
93206
}));
94207
};
208+
209+
this.send = function(channel, payload){
210+
// Low level send
211+
worker.postMessage(JSON.stringify({
212+
"channel":channel,
213+
"payload":payload
214+
}));
215+
};
216+
};
217+
CCLScripting.prototype.BridgedSandbox.prototype.init = function(){
218+
var self = this;
219+
this.addListener("Runtime::alert", function(msg){
220+
alert(msg);
221+
});
222+
this.addListener("Runtime::clear", function(){
223+
self.getContext().clear();
224+
});
225+
this.addListener("Player::action", function(msg){
226+
try{
227+
switch(msg.action){
228+
default:return;
229+
case "play": self.getPlayer().play();break;
230+
case "pause": self.getPlayer().pause();break;
231+
case "seek": self.getPlayer().seek(msg.offset);break;
232+
case "jump": self.getPlayer().jump(msg.params);break;
233+
}
234+
}catch(e){
235+
if(e.stack){
236+
self.getLogger().error(e.stack);
237+
}else{
238+
self.getLogger().error(e.toString());
239+
}
240+
}
241+
});
242+
this.addListener("Runtime:RegisterObject", function(pl){
243+
self.getContext().registerObject(pl.id, pl.data);
244+
});
245+
this.addListener("Runtime:DeregisterObject", function(pl){
246+
self.getContext().deregisterObject(pl.id);
247+
});
248+
this.addListener("Runtime:InvokeMethod", function(pl){
249+
self.getContext().callMethod(pl.id, pl.method, pl.params);
250+
});
251+
};
252+
253+
// Define some unpackers
254+
var ScriptingContext = CCLScripting.prototype.ScriptingContext;
255+
ScriptingContext.prototype.Unpack.Comment = function(stage, data){
256+
this.DOM = document.createElement("div");
257+
258+
this.DOM.appendChild(document.createTextNode(data.text));
259+
260+
this.setFilters = function(params){
261+
for(var i = 0; i < params[0].length; i++){
262+
var filter = params[0][i];
263+
if(filter.type === "blur"){
264+
this.DOM.style.color = "transparent";
265+
this.DOM.style.textShadow = [-filter.params.blurX + "px",
266+
-filter.params.blurY + "px", Math.max(
267+
filter.params.blurX, filter.params.blurY) +
268+
"px"].join(" ");
269+
}else if(filter.type === "glow"){
270+
this.DOM.style.textShadow = [-filter.params.blurX + "px",
271+
-filter.params.blurY + "px", Math.max(
272+
filter.params.blurX, filter.params.blurY) +
273+
"px", "#" + filter.params.color.toString(16)].join(" ");
274+
}
275+
};
276+
};
277+
278+
// Hook child
279+
stage.appendChild(this.DOM);
95280
};
96281
})();

0 commit comments

Comments
 (0)