-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.js
More file actions
235 lines (214 loc) · 6.66 KB
/
main.js
File metadata and controls
235 lines (214 loc) · 6.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
"use strict";
// Difference mode: vibration = abs(previous_value - current_value)
// Maybe it's better for vibrating butt plugs, idk.
// TODO(c4): Try CFAR for this.
// Invert mode: vibration = 100 - current_value
// SPOILER: It won't help, some scipt makers are assholes and they're
// writing it only for strokers. Try find different script.
let client = null; // Buttplug.io client
let devices = []; // List of all devices
let device_latency = 0; // ping to device for funscript offset
let device_latencies; // TODO(c4ll): Make ping individual for each device
let script; // Raw script values
let current_ind = -1; // Script value index that is playing right now
// Finding it on the fly is too slow, so we store it
let script_min_interval = 10; // Minimal interval between device updates
// Required for loop waiting update
let last_power_value = 0; // Previous value for device for difference mode
// Try connect to the buttplug server
const main_connect = async () => {
const address = intiface_address_input.value;
let connector = new Buttplug
.ButtplugBrowserWebsocketClientConnector(address);
client = new Buttplug.ButtplugClient("c4l funscript player");
client.addListener("deviceadded", (device) => {
devices.push(device);
console.log(`Device Connected: ${device.name}`);
});
client.addListener("deviceremoved", (device) => {
devices.pop(device);
console.log(`Device Removed: ${device.name}`)
});
try {
client.connect(connector);
} catch (ex) {
console.error(ex);
};
};
const main_disconnect = async () => {
if (client !== null) {
client.disconnect();
};
client = null;
devices = [];
};
const vibrate_all = async (value) => {
// console.log(`vibrating at ${value}`);
for (const device of devices) {
if (device.vibrateAttributes.length === 0) {
continue;
};
try {
await device.vibrate(value);
} catch (ex) {
console.error(ex);
if (ex instanceof Buttplug.ButtplugDeviceError) {
console.warn("Got device error!");
};
};
};
};
const vibrate_all_stop = async () => {
// console.log("all vibration stopped");
if (client === null) {
return;
};
await client.stopAllDevices();
};
const main_test_vibrate = async () => {
await vibrate_all(vibration_rate_input.value / 100);
await new Promise(r => setTimeout(r, 1000));
await vibrate_all_stop();
};
// Find current power value index in the script
const update_current_ind = () => {
if (script === null) {
return;
};
// Current position in milis
const at = fun_player.currentTime.toFixed(3) * 1000 + device_latency;
current_ind = 0;
// Iterate until power next power value time is lower then current time
while (script.actions[current_ind + 1].at <= at) {
current_ind += 1;
};
};
// Find minimum time between two power values in script
// Required for better idle time waiting without droping frames
const update_interval = () => {
if (script === null) {
return;
};
script_min_interval = -1;
for (let i = 0; i < script.actions.length - 1; i += 1) {
let cur = script.actions[i + 1].at - script.actions[i].at;
if (script_min_interval === -1 || cur < script_min_interval) {
script_min_interval = cur;
};
};
};
intiface_connect_button.onclick = async () => {
await main_connect();
};
intiface_disconnect_button.onclick = async () => {
await main_disconnect();
};
test_vibrate_button.onclick = async () => {
await main_test_vibrate();
};
// On video file uploaded
video_upload.addEventListener("change", function() {
const file = this.files[0];
// I don't really know if I should keep this check. I think users are not
// idiots.
// (if file isn't empty and is a video)
if (file !== null && file.type.startsWith("video/")) {
// Create base64 encoded data stream
const video_url = URL.createObjectURL(file);
// Use it as a video stream
fun_player.src = video_url;
// Fuck browser DOM, because of cause it needs seperate function to
// update video
fun_player.load();
} else {
console.error(`Expected video, got ${file.type}`);
};
});
// On script file uploaded
script_upload.addEventListener("change", function() {
const file = this.files[0];
if (!file) {
return;
};
const reader = new FileReader();
reader.onload = function (event) {
// Some strange json decoding (I thought json is a native JS format)
try {
const script_text = event.target.result;
script = JSON.parse(script_text);
} catch (ex) {
console.error(`Error at parsing funscript: ${ex}`);
return;
};
// Cache some cool values
update_interval();
update_current_ind();
};
// Cause we NEED this fucking functional shit in browser
reader.readAsText(file);
});
// Called every script_min_interval milis
const update_func = async () => {
if (script == null || fun_player.paused) {
return;
};
const at = fun_player.currentTime.toFixed(3) * 1000 + device_latency;
// console.log(at);
// Update current index in script
while (script.actions.length > current_ind + 1 &&
script.actions[current_ind + 1].at <= at) {
current_ind += 1;
};
// If there's no values, just return
if (current_ind === -1) {
return;
};
let power = 0;
// See comment on the top
if (difference_mode_input.checked) {
let value = (script.actions[current_ind].pos / 100);
power = value - last_power_value;
last_power_value = value;
} else {
power = script.actions[current_ind].pos / 100;
if (invert_mode_input.checked) {
power = 1.0 - power;
}
};
// console.log(power);
// No per device support right now.
await vibrate_all(Math.abs(power) *
vibration_rate_input.value / 100);
};
// Main loop
(async () => {
while (true) {
await update_func();
// I miss C's sleep(3)
await new Promise(r => setTimeout(r, script_min_interval));
};
})();
// Update device ping
// Right now it uses battery info for this because it doesn't change
// device's state.
setInterval(async () => {
if (devices.length === 0) {
return;
};
let device = devices[0];
let t0 = performance.now();
await device.battery();
let t1 = performance.now();
device_latency = (t1 - t0) / 2.0;
}, 200);
// Update current index everytime user changes playback
fun_player.addEventListener('play', update_current_ind);
fun_player.addEventListener('seeked', update_current_ind);
// Because if we don't, it will vibrate FOREVER!!
fun_player.addEventListener('pause', () => {
vibrate_all_stop();
});
// Just trying to cache address with fallback value. Shoot me in the head
if (intiface_address_input.value === "") {
intiface_address_input.value = "ws://localhost:12345";
};