efront 4.26.2 → 4.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,136 @@
1
+ var {
2
+ RTCPeerConnection,
3
+ RTCDataChannel,
4
+ RTCSessionDescription,
5
+ RTCIceCandidate,
6
+ } = window;
7
+ var port = location.port;
8
+ if (!port) port = /^https\:/.test(location.href) ? 443 : 80;
9
+ var configuration = {
10
+ iceServers: [
11
+ // { urls: "stun:stun.stunprotocol.org:3478" },
12
+ { urls: "stun:" + location.host + ":" + port }
13
+ ],
14
+ };
15
+
16
+ var enabled = !!RTCPeerConnection;
17
+ class ChatRTC {
18
+ static enabled = enabled;
19
+ enabled = enabled;
20
+ local = null;
21
+ remote = null;
22
+ channel = null;
23
+ localStream = null;
24
+ /**
25
+ * @type {RTCPeerConnection}
26
+ */
27
+ peerConnection = null;
28
+ candidates = [];
29
+ constructor() {
30
+ this.peerConnection = new RTCPeerConnection(configuration);
31
+ }
32
+ async setAnswer(answer) {
33
+ await this.peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
34
+ flushDidate(this);
35
+ }
36
+ async addDidate(candidate) {
37
+ candidate = candidate ? new RTCIceCandidate(candidate) : { candidate: '' };
38
+ if (!this.candidates) {
39
+ await this.peerConnection.addIceCandidate(candidate);
40
+ }
41
+ else {
42
+ this.candidates.push(candidate);
43
+ }
44
+ }
45
+ // 开始获取本地媒体
46
+ async initMedia(audioOnly) {
47
+ var localStream = this.localStream;
48
+ if (!localStream) localStream = this.localStream = await navigator.mediaDevices.getUserMedia({ video: !audioOnly, audio: true });
49
+ var local = this.local;
50
+ local.srcObject = localStream;
51
+ local.play();
52
+ addTracks(this.peerConnection, localStream);
53
+ }
54
+ async createChannel(id, options) {
55
+ return this.peerConnection.createDataChannel(id, options);
56
+ }
57
+ waitChannel() {
58
+ return new Promise((ok) => {
59
+ if (this.channel) return ok(this.channel);
60
+ this.peerConnection.ondatachannel = (event) => {
61
+ this.channel = event.channel;
62
+ ok(this.channel);
63
+ };
64
+ });
65
+ }
66
+ async init(ondate, offer) {
67
+ var peerConnection = this.peerConnection;
68
+ peerConnection.emitDidate = ondate;
69
+ peerConnection.onicecandidate = oncandidate;
70
+ peerConnection.ondatachannel = event => this.channel = event.channel;
71
+ if (offer) return takeOffer(this, offer);
72
+ offer = await peerConnection.createOffer();
73
+ await peerConnection.setLocalDescription(offer);
74
+ return offer;
75
+ }
76
+ async call(ondate, offer) {
77
+ await this.initMedia();
78
+ var peerConnection = this.peerConnection;
79
+ // 处理 ICE 候选
80
+ peerConnection.remote = this.remote;
81
+ peerConnection.ontrack = ontrack;
82
+ return this.init(ondate, offer);
83
+ };
84
+ async hangup() {
85
+ var { peerConnection, localStream, local, remote } = this;
86
+ if (peerConnection) peerConnection.close();
87
+ stopTracks(localStream);
88
+ this.tracks = null;
89
+ this.candidates = null;
90
+ if (local) local.srcObject = null;
91
+ if (remote) remote.srcObject = null;
92
+ };
93
+ }
94
+ async function takeOffer(rtc, offer) {
95
+ var pc = rtc.peerConnection;
96
+ await pc.setRemoteDescription(new RTCSessionDescription(offer));
97
+ var answer = await pc.createAnswer();
98
+ await pc.setLocalDescription(answer);
99
+ flushDidate(rtc);
100
+ return answer;
101
+ }
102
+
103
+ /**
104
+ * @param {ChatRtc} rtc
105
+ */
106
+ function flushDidate(rtc) {
107
+ if (rtc.candidates) {
108
+ var cds = rtc.candidates;
109
+ rtc.candidates = null;
110
+ var peerConnection = rtc.peerConnection;
111
+ for (var d of cds) peerConnection.addIceCandidate(d);
112
+ }
113
+ }
114
+ var ontrack = function (event) {
115
+ var remote = this.remote;
116
+ remote.srcObject = event.streams[0];
117
+ };
118
+ var addTracks = function (peerConnection, localStream) {
119
+ var tracks = localStream?.getTracks();
120
+ // 将本地流添加到 PeerConnection
121
+ if (tracks) for (var track of tracks) {
122
+ peerConnection.addTrack(track, localStream);
123
+ }
124
+ };
125
+ var stopTracks = function (localStream) {
126
+ var tracks = localStream?.getTracks();
127
+ if (tracks) for (var track of tracks) {
128
+ track.stop();
129
+ }
130
+ }
131
+ var oncandidate = function (event) {
132
+ var candidate = event.candidate;
133
+ if (!candidate) return this.emitDidate(null);
134
+ var { sdpMid, candidate, sdpMLineIndex, usernameFragment } = candidate;
135
+ this.emitDidate({ sdpMid, candidate, sdpMLineIndex, usernameFragment });
136
+ }
@@ -0,0 +1,275 @@
1
+ <style>
2
+ & {
3
+ width: 100%;
4
+ height: 100%;
5
+ position: fixed;
6
+ left: 0;
7
+ top: 0;
8
+ right: 0;
9
+ bottom: 0;
10
+ background: #111;
11
+ }
12
+
13
+ dv {
14
+ border: 1px solid;
15
+ width: 240px;
16
+ height: 180px;
17
+ right: 10px;
18
+ bottom: 10px;
19
+ display: block;
20
+ position: absolute;
21
+ color: #fff9;
22
+ background: #222;
23
+ z-index: 1;
24
+
25
+ >span {
26
+ position: relative;
27
+ }
28
+
29
+ &[full] {
30
+ z-index: 0 !important;
31
+ width: auto !important;
32
+ height: auto !important;
33
+ left: 0 !important;
34
+ right: 0 !important;
35
+ bottom: 0 !important;
36
+ top: 0 !important;
37
+ margin: 0 !important;
38
+ padding: 0 !important;
39
+ border: none;
40
+ }
41
+
42
+ &:after {
43
+ display: block;
44
+ content: "";
45
+ left: 0;
46
+ bottom: 0;
47
+ right: 0;
48
+ top: 0;
49
+ position: absolute;
50
+ }
51
+
52
+ >video {
53
+ display: block;
54
+ position: absolute;
55
+ left: 0;
56
+ right: 0;
57
+ bottom: 0;
58
+ top: 0;
59
+ width: 100%;
60
+ height: 100%;
61
+ }
62
+ }
63
+
64
+ btn.button {
65
+ display: block;
66
+ position: absolute;
67
+ width: 60px;
68
+ line-height: 60px;
69
+ height: 60px;
70
+ border-radius: 30px;
71
+ margin: -23px;
72
+ z-index: 2;
73
+ }
74
+
75
+ [accept] {
76
+ left: 50%;
77
+ top: 50%;
78
+ background: #fff2;
79
+ }
80
+
81
+ [hangup] {
82
+ left: 50%;
83
+ bottom: 30px;
84
+ background: #913;
85
+ }
86
+
87
+ [info],
88
+ [error] {
89
+ display: block;
90
+ position: absolute;
91
+ top: 50%;
92
+ z-index: 1;
93
+ left: 0;
94
+ right: 0;
95
+ text-align: center;
96
+ line-height: 40px;
97
+ margin-top: -20px;
98
+ color: #fff;
99
+ }
100
+
101
+ [error] {
102
+ display: none;
103
+ }
104
+
105
+ &[error] {
106
+
107
+ [info],
108
+ [accept] {
109
+ display: none;
110
+ }
111
+
112
+ [error] {
113
+ display: block;
114
+ }
115
+ }
116
+ </style>
117
+ <div>
118
+ <dv full>
119
+ <video playsinline autoplay webkit-playsinline ondblclick="preventDefault"></video>
120
+ <span -bind="remoteUser.name"></span>
121
+ </dv>
122
+ <dv style="opacity: 0">
123
+ <video muted playsinline webkit-playsinline ondblclick="preventDefault"></video>
124
+ <span></span>
125
+ </dv>
126
+ <btn danger hangup @click="hangup">挂断</btn>
127
+ <btn accept -show="isRemote&&!started" @click="accept()">接听</btn>
128
+ <span error>${i18n`无法打开媒体设备`}</span>
129
+ <span info -show="started">
130
+ <span -bind="status()"></span>
131
+ &nbsp;<time></time>
132
+ </span>
133
+ </div>
134
+ <script>
135
+ var chatrtc = new ChatRTC;
136
+ // fullscreen.open();
137
+ var status = function () {
138
+ return acceptTime ? i18n`通话中` : rejectTime ? i18n`无应答` : i18n`正在呼叫`
139
+ };
140
+ var btn = button;
141
+ var dvs;
142
+ var callingStart = new Date, acceptTime = 0;
143
+ var started = false;
144
+ var interval = 0;
145
+ var timeNode = document.createTextNode("");
146
+ var rejectTime = 0;
147
+ care(this, function ([type, sender, data]) {
148
+ if (sender !== remoteUser.id) return;
149
+ switch (type) {
150
+ case "rtc-close":
151
+ if (acceptTime) return remove(this);
152
+ rejectTime = new Date;
153
+ if (remoteOffer) return remove(this);
154
+ break;
155
+ case "rtc-accept":
156
+ acceptTime = new Date;
157
+ chatrtc.setAnswer(data);
158
+ break;
159
+ case "rtc-didate":
160
+ chatrtc.addDidate(data);
161
+ break;
162
+ }
163
+ });
164
+ interval = setInterval(function () {
165
+ var toNum2 = a => a < 10 ? "0" + a : a;
166
+ var now = new Date;
167
+ var time = callingStart;
168
+ if (acceptTime) time = acceptTime;
169
+ var time = (new Date - time) / 1000 | 0;
170
+ if (time > 60 && !acceptTime && !rejectTime) {
171
+ rejectTime = time;
172
+ }
173
+ if (rejectTime && now - rejectTime > 16 * 1000) {
174
+ remove(page);
175
+ }
176
+ var value = [0, 0, 0, 0];
177
+ if (time >= 86400) {
178
+ value[0] = time / 86400 | 0;
179
+ time -= value[0] * 86400;
180
+ }
181
+ if (time >= 3600) {
182
+ value[1] = time / 3600 | 0;
183
+ time -= value[1] * 3600;
184
+ }
185
+ if (time >= 60) {
186
+ value[2] = time / 60 | 0;
187
+ time -= value[2] * 60;
188
+ }
189
+ value[3] = time;
190
+ var [date, ...time] = value;
191
+ if (date) timeNode.nodeValue = i18n`${date}天 ` + time.map(toNum2).join(":");
192
+ else if (time[0]) timeNode.nodeValue = time.map(toNum2).join(":");
193
+ else timeNode.nodeValue = time.slice(1).map(toNum2).join(":");
194
+
195
+ }, 20);
196
+ var time = function (elem) {
197
+ elem.appendChild(timeNode);
198
+ };
199
+ on('remove')(this, async function () {
200
+ await chatrtc.hangup();
201
+ chatrtc.send = null;
202
+ fullscreen.close();
203
+ chatrtc.remote = null;
204
+ chatrtc.local = null;
205
+ clearInterval(interval);
206
+ })
207
+ var canplay = function () {
208
+ var dv = this.parentNode;
209
+ var ratio = Math.min(dv.clientWidth / this.videoWidth, dv.clientHeight / this.videoHeight);
210
+ if (ratio) {
211
+ move.setSize(dv, [this.videoWidth * ratio, this.videoHeight * ratio]);
212
+ dv.style.opacity = 1;
213
+ }
214
+ }
215
+ once('mounted')(this, function () {
216
+ var [dv1, dv2, hangup, accept] = this.children;
217
+ dvs = [dv1, dv2];
218
+ drag.on(dv1, false);
219
+ drag.on(dv2, false);
220
+ drag.on(hangup, false);
221
+ drag.on(accept, false);
222
+ on("dblclick")(dv1, dblclick);
223
+ on("dblclick")(dv2, dblclick);
224
+ var v1 = chatrtc.remote = dv1.firstElementChild;
225
+ var v2 = chatrtc.local = dv2.firstElementChild;
226
+ v1.oncanplay = canplay;
227
+ v2.oncanplay = canplay;
228
+ if (!isRemote) start();
229
+ });
230
+ var accept = async function () {
231
+ await start();
232
+ acceptTime = new Date;
233
+ };
234
+ var onCandidate = function (candidate) {
235
+ cast(page, ["didate", candidate]);
236
+ };
237
+ var start = async function () {
238
+ try {
239
+ if (!remoteOffer) {
240
+ var offer = await chatrtc.call(onCandidate, remoteOffer);
241
+ cast(page, ['offer', offer]);
242
+ }
243
+ else {
244
+ var answer = await chatrtc.call(onCandidate, remoteOffer);
245
+ cast(page, ['accept', answer]);
246
+ }
247
+ }
248
+ catch {
249
+ page.setAttribute('error', '');
250
+ }
251
+ finally {
252
+ started = true;
253
+ }
254
+ render.refresh();
255
+ }
256
+ var page = this;
257
+ var hangup = async function () {
258
+ cast(page, ['hangup']);
259
+ remove(page);
260
+ };
261
+ var dblclick = function (a) {
262
+ if (this.hasAttribute('full')) return;
263
+ var style = this.getAttribute('style');
264
+ if (dvs.length !== 2) style = '';
265
+ dvs.forEach(v => {
266
+ if (v === this) v.setAttribute('full', ''), v.nodrag = true;
267
+ else v.removeAttribute('full'), v.nodrag = false, v.setAttribute('style', style), canplay.call(v);
268
+ });
269
+ };
270
+ this.onback = e => false;
271
+ var preventDefault = e => e.preventDefault();
272
+ var [remoteUser, localUser, remoteOffer] = arguments;
273
+ var isRemote = !!remoteOffer;
274
+
275
+ </script>
@@ -1,3 +1,8 @@
1
+ <style>
2
+ [left] {
3
+ float: left;
4
+ }
5
+ </style>
1
6
  <div head mount>
2
7
  <btn -if="users.length>0" class="menubtn" @click="showList=!showList" type_="showList?'default':'white'">
3
8
  <i></i>
@@ -7,6 +12,7 @@
7
12
  <template -if="user">
8
13
  <span -bind="user.name"></span>
9
14
  &nbsp;<span class="id">(<span -bind="user.id"></span>)</span>
15
+ <a left -if="user.id!==localid" @click="call()">呼叫</a>
10
16
  </template>
11
17
  <template -elseif="title" -src="title"> </template>
12
18
  <close @click="remove()"></close>