Compare commits

...

4 Commits

Author SHA1 Message Date
b72be4e004 加入 git 地址 2024-09-04 20:15:57 +08:00
c3c0a37a41 替换发送为图标 2024-03-08 14:04:12 +08:00
03e19fbf58 移除对 indexeddb 的额外封装 2024-03-08 11:03:09 +08:00
099e36e1b5 使用 idb 简化 indexeddb 操作 2024-03-08 11:02:37 +08:00
7 changed files with 75 additions and 156 deletions

View File

@@ -1,11 +1,7 @@
# webRTC # webRTC
webrtc 实现的 p2p 信道 webrtc 实现的 p2p 信道
* 不要将 JS 文件编译, 使用便于阅读的源码发布 ```bash
* 避免依赖服务器提供静态文件, 使用WEBRTC在浏览器之间共享
* 内核策略, 在浏览器中查看和编辑文件
```
# 使用 git 克隆到本地或者直接下载zip压缩包 # 使用 git 克隆到本地或者直接下载zip压缩包
git clone git@git.satori.love:LaniakeaSupercluster/webrtc.git git clone git@git.satori.love:LaniakeaSupercluster/webrtc.git
cd webrtc cd webrtc

5
public/remove.js Normal file
View File

@@ -0,0 +1,5 @@
import { Chessboard } from './ChineseChess.js'
// 中国象棋
const chessboard = new Chessboard()
chessboard.绘制棋盘({比例: 48, 边距: 20})

13
public/send.svg Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 495.003 495.003" xml:space="preserve">
<g id="XMLID_51_">
<path id="XMLID_53_" d="M164.711,456.687c0,2.966,1.647,5.686,4.266,7.072c2.617,1.385,5.799,1.207,8.245-0.468l55.09-37.616
l-67.6-32.22V456.687z"/>
<path id="XMLID_52_" d="M492.431,32.443c-1.513-1.395-3.466-2.125-5.44-2.125c-1.19,0-2.377,0.264-3.5,0.816L7.905,264.422
c-4.861,2.389-7.937,7.353-7.904,12.783c0.033,5.423,3.161,10.353,8.057,12.689l125.342,59.724l250.62-205.99L164.455,364.414
l156.145,74.4c1.918,0.919,4.012,1.376,6.084,1.376c1.768,0,3.519-0.322,5.186-0.977c3.637-1.438,6.527-4.318,7.97-7.956
L494.436,41.257C495.66,38.188,494.862,34.679,492.431,32.443z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 973 B

View File

@@ -41,6 +41,7 @@ export default class Chat {
boxShadow: '0 0 1rem #eee', boxShadow: '0 0 1rem #eee',
border: 'none', border: 'none',
outline: 'none', outline: 'none',
borderRadius: '2rem'
}, },
onkeydown: event => { onkeydown: event => {
event.stopPropagation() event.stopPropagation()
@@ -79,7 +80,6 @@ export default class Chat {
} }
}), }),
Button({ Button({
textContent: '发送(Enter)',
onclick: event => { onclick: event => {
const text = event.target.previousSibling.value.trim() const text = event.target.previousSibling.value.trim()
if (text) { if (text) {
@@ -88,10 +88,11 @@ export default class Chat {
} }
}, },
style: { style: {
padding: '.5rem 1rem', width: '1.2rem',
boxSizing: 'border-box', height: '1.2rem',
boxShadow: '0 0 1rem #eee', border: 'none',
borderRadius: '1rem', background: 'url("/send.svg") no-repeat center / cover',
margin: 'auto 0 auto -2.6rem'
} }
}), }),
] ]
@@ -311,7 +312,7 @@ export default class Chat {
maxWidth: '24rem', maxWidth: '24rem',
borderRadius: '1rem', borderRadius: '1rem',
listStyle: 'none', listStyle: 'none',
backgroundColor : 'rgba(255,255,255,.9)', backgroundColor: 'rgba(255,255,255,.9)',
}, },
children: [ children: [
createElement({ createElement({

View File

@@ -1,92 +0,0 @@
export default class IndexedDB {
constructor(databaseName, databaseVersion) {
this.databaseName = databaseName
this.databaseVersion = databaseVersion
this.db = null
}
open(name) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.databaseName, this.databaseVersion)
request.onerror = (event) => {
reject(event.target.error)
}
request.onsuccess = (event) => {
this.db = event.target.result
resolve(this.db)
}
request.onupgradeneeded = (event) => {
const db = event.target.result
if (!db.objectStoreNames.contains(name)) {
db.createObjectStore(name, { keyPath: 'id' })
console.log('store created:', name)
}
}
})
}
async store(name) {
if (!this.db) await this.open(name)
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([name], 'readwrite')
const objectStore = transaction.objectStore(name)
resolve(objectStore)
})
}
getAll(name) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([name], 'readonly')
const objectStore = transaction.objectStore(name)
const request = objectStore.getAll()
request.onerror = (event) => {
reject(event.target.error)
}
request.onsuccess = (event) => {
resolve(event.target.result)
}
})
}
add(name, data) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([name], 'readwrite')
const objectStore = transaction.objectStore(name)
const request = objectStore.add(data)
request.onerror = (event) => {
reject(event.target.error)
}
request.onsuccess = (event) => {
resolve(event.target.result)
}
})
}
put(name, data) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([name], 'readwrite')
const objectStore = transaction.objectStore(name)
const request = objectStore.put(data)
request.onerror = (event) => {
reject(event.target.error)
}
request.onsuccess = (event) => {
resolve(event.target.result)
}
})
}
delete(name, id) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([name], 'readwrite')
const objectStore = transaction.objectStore(name)
const request = objectStore.delete(id)
request.onerror = (event) => {
reject(event.target.error)
}
request.onsuccess = (event) => {
resolve(event.target.result)
}
})
}
}

View File

@@ -1,6 +1,4 @@
import 'virtual:windi.css' import * as idb from 'idb-keyval'
import IndexedDB from './indexeddb.js'
import MusicList from './music.js' import MusicList from './music.js'
import ClientList from './client.js' import ClientList from './client.js'
import Chat from './chat.js' import Chat from './chat.js'
@@ -11,12 +9,6 @@ window.Buffer = Buffer
window.process = process window.process = process
import { parseBlob } from 'music-metadata-browser' import { parseBlob } from 'music-metadata-browser'
import { Chessboard } from './ChineseChess.js'
// 中国象棋
const chessboard = new Chessboard()
chessboard.绘制棋盘({比例: 48, 边距: 20})
// 缓冲分片发送 // 缓冲分片发送
const CHUNK_SIZE = 1024 * 64 // 默认每个块的大小为128KB const CHUNK_SIZE = 1024 * 64 // 默认每个块的大小为128KB
const THRESHOLD = 1024 * 1024 // 默认缓冲区的阈值为1MB const THRESHOLD = 1024 * 1024 // 默认缓冲区的阈值为1MB
@@ -31,11 +23,11 @@ function appendBuffer(buffer1, buffer2) {
} }
// 读取本地音乐列表并标识为缓存状态(本地缓存) // 读取本地音乐列表并标识为缓存状态(本地缓存)
const database = new IndexedDB('musicDatabase', 1) const musicStore = idb.createStore('database', 'music')
await database.store('musicObjectStore') // 音乐(为什么会用这么丑的格式呢)
// 读取本地音乐列表并标识为缓存状态(本地缓存) // 读取本地音乐列表并标识为缓存状态(本地缓存)
const list = await Promise.all((await database.getAll('musicObjectStore')).map(async item => { const list = await idb.values(musicStore)
for (const item of list) {
if (!item.picture && item.picture !== false) { if (!item.picture && item.picture !== false) {
console.log('提取封面', item.name) console.log('提取封面', item.name)
const blob = new Blob([item.arrayBuffer], { type: item.type }) const blob = new Blob([item.arrayBuffer], { type: item.type })
@@ -48,10 +40,10 @@ const list = await Promise.all((await database.getAll('musicObjectStore')).map(a
} else { } else {
item.picture = false item.picture = false
} }
database.put('musicObjectStore', item) idb.set(item.id, item, musicStore)
} }
return { save: true, ...item } item.save = true
})) }
// 读取本地用户名(本地缓存) // 读取本地用户名(本地缓存)
const name = localStorage.getItem('username') ?? '匿' const name = localStorage.getItem('username') ?? '匿'
@@ -88,8 +80,7 @@ const musicList = new MusicList({
onlike: (item, list) => { onlike: (item, list) => {
console.log('喜欢音乐', item.name) console.log('喜欢音乐', item.name)
if (item.arrayBuffer) { if (item.arrayBuffer) {
//musicStore.add(item) idb.set(item.id, item, musicStore)
database.add('musicObjectStore', item)
clientList.send('base', JSON.stringify({ clientList.send('base', JSON.stringify({
type: 'set_music_list', type: 'set_music_list',
list: list.map(({ id, name, size, type }) => ({ id, name, size, type })) list: list.map(({ id, name, size, type }) => ({ id, name, size, type }))
@@ -99,14 +90,30 @@ const musicList = new MusicList({
onunlike: (item, list) => { onunlike: (item, list) => {
console.log('取消喜欢', item.name) console.log('取消喜欢', item.name)
if (item.arrayBuffer) { if (item.arrayBuffer) {
database.delete('musicObjectStore', item.id) idb.del(item.id, musicStore)
//musicStore.delete(item.id)
clientList.send('base', JSON.stringify({ clientList.send('base', JSON.stringify({
type: 'set_music_list', type: 'set_music_list',
list: list.map(({ id, name, size, type }) => ({ id, name, size, type })) list: list.map(({ id, name, size, type }) => ({ id, name, size, type }))
})) }))
} }
}, },
onsetlrc(item) {
const input = document.createElement('input')
input.type = 'file'
input.accept = '.lrc'
input.onchange = function () {
const file = this.files[0]
const reader = new FileReader()
reader.onload = function (e) {
const lrc = e.target.result
console.log(lrc)
console.log(item)
idb.set(item.id, { lrc, ...item }, musicStore)
}
reader.readAsText(file)
}
input.click()
},
onban: item => { onban: item => {
//console.info('禁止音乐', item.name) //console.info('禁止音乐', item.name)
}, },
@@ -115,8 +122,7 @@ const musicList = new MusicList({
}, },
onremove: item => { onremove: item => {
//console.info('移除音乐', item.name) //console.info('移除音乐', item.name)
//musicStore.delete(item.id) idb.del(item.id, musicStore)
database.delete('musicObjectStore', item.id)
}, },
onadd: (item, list) => { onadd: (item, list) => {
//console.info('添加音乐', item.name) //console.info('添加音乐', item.name)
@@ -173,29 +179,6 @@ const chat = new Chat({
} }
}) })
//// 与每个客户端保持心跳
//clientList.setChannel('ping', {
// onopen: async (event, client) => {
// console.log('打开信道', event.target.label)
// clientList.sendto(client.id, 'ping', JSON.stringify({ type: 'ping' }))
// },
// onmessage: async (event, client) => {
// const data = JSON.parse(event.data)
// if (data.type === 'ping') {
// console.log(client.name, '心跳:', data)
// clientList.sendto(client.id, 'ping', JSON.stringify({ type: 'pong' }))
// return
// }
// if (data.type === 'pong') {
// console.log(client.name, '心跳:', data)
// await new Promise((resolve) => setTimeout(resolve, 5000))
// clientList.sendto(client.id, 'ping', JSON.stringify({ type: 'ping' }))
// return
// }
// console.log('未知类型:', data.type)
// }
//})
// 与每个客户端都建立聊天信道 // 与每个客户端都建立聊天信道
clientList.setChannel('chat', { clientList.setChannel('chat', {
onopen: async (event, client) => { onopen: async (event, client) => {
@@ -394,3 +377,16 @@ if (localStorage.getItem('avatar')) {
if (localStorage.getItem('username')) { if (localStorage.getItem('username')) {
document.title = localStorage.getItem('username') document.title = localStorage.getItem('username')
} }
// 链接 GIT 仓库
const git = document.createElement('a')
git.href = 'https://git.satori.love/laniakeaSupercluster/webrtc'
git.textContent = 'Git'
git.target = '_blank'
git.style.position = 'absolute'
git.style.bottom = 0
git.style.right = 0
git.style.padding = '.5rem'
git.style.textDecoration = 'none'
git.style.color = '#555'
document.body.appendChild(git)

View File

@@ -1,8 +1,8 @@
import { Img, Span, Button, List, ListItem, UploadMusic, createElement } from './weigets.js' import { Img, Span, Button, List, ListItem, UploadMusic, createElement } from './weigets.js'
export default class MusicList { export default class MusicList {
constructor({ list = [], EventListeners = {}, onplay, onstop, onadd, onremove, onlike, onunlike, onban, onload }) { constructor({ list = [], EventListeners = {}, onsetlrc, onplay, onstop, onadd, onremove, onlike, onunlike, onban, onload }) {
this.event = { onplay, onstop, onadd, onremove, onlike, onunlike, onban, onload } this.event = { onsetlrc, onplay, onstop, onadd, onremove, onlike, onunlike, onban, onload }
// 添加音乐播放器 // 添加音乐播放器
this.audio = new Audio() this.audio = new Audio()
@@ -16,12 +16,6 @@ export default class MusicList {
this.audio.addEventListener('ended', () => { this.audio.addEventListener('ended', () => {
this.next() this.next()
}) })
//this.audio.addEventListener('timeupdate', () => {
// console.log(this.audio.currentTime)
//})
//this.audio.addEventListener('error', event => {
// console.error('音乐播放器错误:', event)
//})
this.ul = List({ this.ul = List({
classList: ['music-list'], classList: ['music-list'],
style: { style: {
@@ -324,10 +318,16 @@ export default class MusicList {
this.like(item) this.like(item)
} }
} }
}),
Button({
textContent:'lrc',
onclick: () => {
this.event.onsetlrc?.(item)
}
}) })
] ]
})) }))
this.event.onadd(item, this.list) this.event.onadd?.(item, this.list)
} }
async remove(item) { async remove(item) {
this.ul.querySelector(`#${item.id}`)?.remove() this.ul.querySelector(`#${item.id}`)?.remove()