Compare commits

...
2 Commits
Author SHA1 Message Date
539943419 fa357afdd8 update: webrtc 2024-12-13 16:24:32 +08:00
539943419 7434accb0e update 2024-12-13 11:17:19 +08:00
8 changed files with 877 additions and 187 deletions

No files matched your search

+3 -1
View File
@@ -1 +1,3 @@
node_modules node_modules
test.*
.vscode
+191 -158
View File
@@ -1,194 +1,227 @@
# 与 css 相关,多学习 css # 与 css 相关,多学习 css
## css inset ## css inset
- inset : top | right| bottom | left
- 使用例: - 同时设置上下左右等多个属性值
```css - inset : top | right| bottom | left
.className { - 使用例:
inset: 4px 8px; /* 上下 | 左右 */ ```css
inset: 5px 15px 10px; /* 上 | 左右 | 下 */ .className {
inset: 2.4em 3em 3em 3em; /* 上 | 右 | 下 | 左 */ inset: 4px 8px; /* 上下 | 左右 */
} inset: 5px 15px 10px; /* 上 | 左右 | 下 */
``` inset: 2.4em 3em 3em 3em; /* 上 | 右 | 下 | 左 */
}
```
## css 过渡 transition ## css 过渡 transition
- 有transition-property、transition-duration、transition-timing-function 和 transition-delay这几个属性,不过一般使用时应该都是用transition合起来写。
- 使用例:
```css - 有 transition-property、transition-duration、transition-timing-function 和 transition-delay 这几个属性,不过一般使用时应该都是用 transition 合起来写。
.className { - 使用例:
/* property name | duration | timing function | delay */
transition: margin-right 4s ease-in-out 1s;
/* Apply to 2 properties */ ```css
transition: margin-right 4s, color 1s; .className {
} /* property name | duration | timing function | delay */
``` transition: margin-right 4s ease-in-out 1s;
/* Apply to 2 properties */
transition: margin-right 4s, color 1s;
}
```
## css 动画 滚动条驱动 ## css 动画 滚动条驱动
- animation-timeline
- 有时间再写 - animation-timeline
- 有时间再写
## css 动画 ## css 动画
- 分为“定义”与“使用”两个阶段。 - 分为“定义”与“使用”两个阶段。
- “定义”阶段的 keyword 为@keyframes,例: - “定义”阶段的 keyword 为@keyframes,例:
```scss ```scss
// 每个时间点填对应时间点的style // 每个时间点填对应时间点的style
@keyframes animationName { @keyframes animationName {
0% { 0% {
border-radius: 1px; border-radius: 1px;
} }
50% { 50% {
border-radius: 12px; border-radius: 12px;
} }
100% { 100% {
border-radius: 123px; border-radius: 123px;
} }
} }
``` ```
- “使用”阶段的 keyword 为 animation,使用 animation 绑定动画,例 - “使用”阶段的 keyword 为 animation,使用 animation 绑定动画,例
```scss ```scss
div { div {
animation: animationName 5s; animation: animationName 5s;
} }
``` ```
- animation 有许多属性 - animation 有许多属性
```scss ```scss
div { div {
// 绑定的动画名 // 绑定的动画名
animation-name: animationName; animation-name: animationName;
// 动画执行总时长 // 动画执行总时长
animation-duration: 5s; animation-duration: 5s;
// 速度曲线。默认是 "ease"。 // 速度曲线。默认是 "ease"。
animation-timing-function: linear; animation-timing-function: linear;
animation-delay: 2s; animation-delay: 2s;
// 播放次数,默认1 // 播放次数,默认1
animation-iteration-count: infinite; animation-iteration-count: infinite;
// 是否在下一个周期逆向播放 // 是否在下一个周期逆向播放
animation-direction: alternate; animation-direction: alternate;
// 是否正在播放() // 是否正在播放()
animation-play-state: running; animation-play-state: running;
// 简写 // 简写
animation: animationName 5s linear 2s infinite alternate; animation: animationName 5s linear 2s infinite alternate;
// 多个动画之间使用逗号进行隔开 // 多个动画之间使用逗号进行隔开
animation: a1..., a2...; animation: a1..., a2...;
} }
``` ```
## before 与 after ## before 与 after
- 伪元素,通过 content 来指定内容,before 就是在前面,after 就是在后面(哇好有道理呢) - 伪元素,通过 content 来指定内容,before 就是在前面,after 就是在后面(哇好有道理呢)
- 用途一般是加个 icon 啥的,content 可以为 url(...) - 用途一般是加个 icon 啥的,content 可以为 url(...)
- 不能应用在替换元素上,比如 `<img>` - 不能应用在替换元素上,比如 `<img>`
- 伪元素不支持嵌套,也就是说伪元素的父元素不可以为伪元素 - 伪元素不支持嵌套,也就是说伪元素的父元素不可以为伪元素
使用例: 使用例:
```css ```css
a { a {
color: #0000ff; color: #0000ff;
text-decoration: none; text-decoration: none;
} }
a::before { a::before {
content: '🔗'; content: '🔗';
} }
``` ```
- 使用 inherit 使子元素拥有与父元素相同的特定属性值 - 使用 inherit 使子元素拥有与父元素相同的特定属性值
使用例: 使用例:
```css ```css
div { div {
position: relative; position: relative;
background: url('your-image-url.jpg'); background: url('your-image-url.jpg');
} }
div::before { div::before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
background: inherit; background: inherit;
z-index: -1; z-index: -1;
} }
``` ```
## !important 相关 ## !important 相关
- 不到万不得已不要用,是不好的写法 - 不到万不得已不要用,是不好的写法
- 如果你的 important 不够 important,那可以写 class 的时候多写点前缀,例: - 如果你的 important 不够 important,那可以写 class 的时候多写点前缀,例:
```scss ```scss
// 一般 // 一般
.ant-slider-rail { .ant-slider-rail {
background-color: rgb(141, 93, 56) !important; background-color: rgb(141, 93, 56) !important;
} }
// 优先级特别高 // 优先级特别高
.slider .ant-slider .ant-slider-rail { .slider .ant-slider .ant-slider-rail {
background-color: rgb(141, 93, 56) !important; background-color: rgb(141, 93, 56) !important;
} }
``` ```
## xx[a=xxx]的使用 ## xx[a=xxx]的使用
- 这玩意叫属性选择器 - 这玩意叫属性选择器
- 案例: - 案例:
```css
a {
color: blue;
}
/* 以 "#" 开头的页面内部链接 */
a[href^='#'] {
background-color: gold;
}
/* 包含 "example" 的链接 */
a[href*='example'] {
background-color: silver;
}
/* 包含 "insensitive" 的链接,不区分大小写 */
a[href*='insensitive' i] {
color: cyan;
}
/* 包含 "cAsE" 的链接,区分大小写 */
a[href*='cAsE' s] {
color: pink;
}
/* 以 ".org" 结尾的链接 */
a[href$='.org'] {
color: red;
}
/* 以 "https" 开始,".org" 结尾的链接 */
a[href^='https'][href$='.org'] {
color: green;
}
```
- 属性不一定要是 href 什么的,自己定也没关系。
- 案例:
```css
/* 将所有包含 `lang` 属性的 <div> 元素的字重设为 bold */
div[lang] {
font-weight: bold;
}
/* 将所有语言为美式英语的 <div> 元素的文本颜色设为蓝色 */
div[lang~='en-us'] {
color: blue;
}
/* 将所有语言为葡萄牙语的 <div> 元素的文本颜色设为绿色 */
div[lang='pt'] {
color: green;
}
```
## grid
```css ```css
a { .container {
color: blue; display: grid;
grid-template-columns: repeat(2, 100px 20px 80px);
/* repeat 配合auto-fill/auto-fit 配合minmax 实现自适应布局 */
grid-template-columns: 1fr 1fr minmax(100px, 1fr);
grid-row-gap: 20px;
grid-column-gap: 20px;
/* 先行后列,尽可能填满 */
grid-auto-flow: row dense;
} }
.item-1 {
/* 以 "#" 开头的页面内部链接 */ /* 边框所对应的网格线 */
a[href^='#'] { /* grid-column属性是grid-column-start和grid-column-end的合并简写形式,grid-row属性是grid-row-start属性和grid-row-end的合并简写形式。 */
background-color: gold; grid-column-start: 2;
} grid-column-end: 4;
grid-column: 2/4;
/* 包含 "example" 的链接 */ grid-row: 1/2;
a[href*='example'] {
background-color: silver;
}
/* 包含 "insensitive" 的链接,不区分大小写 */
a[href*='insensitive' i] {
color: cyan;
}
/* 包含 "cAsE" 的链接,区分大小写 */
a[href*='cAsE' s] {
color: pink;
}
/* 以 ".org" 结尾的链接 */
a[href$='.org'] {
color: red;
}
/* 以 "https" 开始,".org" 结尾的链接 */
a[href^='https'][href$='.org'] {
color: green;
} }
``` ```
## point-event
- 属性不一定要是 href 什么的,自己定也没关系。 当为none时,所有点击事件无效
- 案例:
```css ##
/* 将所有包含 `lang` 属性的 <div> 元素的字重设为 bold */
div[lang] {
font-weight: bold;
}
/* 将所有语言为美式英语的 <div> 元素的文本颜色设为蓝色 */
div[lang~='en-us'] {
color: blue;
}
/* 将所有语言为葡萄牙语的 <div> 元素的文本颜色设为绿色 */
div[lang='pt'] {
color: green;
}
```
+99 -1
View File
@@ -1,3 +1,101 @@
# 杂谈 记录一些碎碎念 # 杂谈 记录一些碎碎念
--- ---
npx用来临时使用一个库(如rollup)
npx 用来临时使用一个库(如 rollup)
---
强缓存和协商缓存的 header 参数
强缓存:
Expires:过期时间,如果设置了时间,则浏览器会在设置的时间内直接读取缓存,不再请求
Cache-Control:当值设为 max-age=300 时,则代表在这个请求正确返回时间(浏览器也会记录下来)的 5 分钟内再次加载资源,就会命中强缓存。
cache-control:除了该字段外,还有下面几个比较常用的设置值:
(1) max-age:用来设置资源(representations)可以被缓存多长时间,单位为秒;
(2) s-maxage:和 max-age 是一样的,不过它只针对代理服务器缓存而言;
(3)public:指示响应可被任何缓存区缓存;
(4)private:只能针对个人用户,而不能被代理服务器缓存;
(5)no-cache:强制客户端直接向服务器发送请求,也就是说每次请求都必须向服务器发送。服务器接收到 请求,然后判断资源是否变更,是则返回新内容,否则返回 304,未变更。这个很容易让人产生误解,使人误 以为是响应不被缓存。实际上 Cache-Control: no-cache 是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。
(6)no-store:禁止一切缓存(这个才是响应不被缓存的意思)。
---
mvvm:
vm: 关注绑定器,将数据和视图绑定,自动更新,低耦合。model仅关注数据更新
---
垃圾回收相关:
window.gc: 手动调用垃圾回收
现代主要使用标记清除法。
进入执行环境的时候标记上。
离开环境的时候直接回收。
常见的内存泄漏:
全局变量,解决方式: 全局变量
定时器未清除:记得及时清除
闭包: 解决方式: 及时设置为null
dom元素删除后未清除: 解决方式: 及时设置为null
栈(Stack)
特点:栈是一种后进先出(LIFO, Last In First Out)的数据结构。
存储内容:用于存储简单的数据类型(如数字、字符串、布尔值)和函数调用上下文。
内存分配和释放:内存分配和释放非常快速,直接在栈顶操作。函数调用结束后,栈帧自动释放。
作用:主要用于管理函数调用和局部变量。
堆(Heap)
特点:堆是一种无序的数据结构,用于动态分配内存。
存储内容:用于存储复杂的数据类型(如对象、数组)。
内存分配和释放:内存分配比较灵活,但管理和回收内存需要垃圾回收机制(GC, Garbage Collection)来处理。
作用:用于存储需要动态分配的较大数据块和复杂数据结构。
---
babel相关:
Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
---
es6几个主要的:
1. let, const
2. 箭头函数
3. class
4. 模板字符串
5. 解构
6. 函数传参默认值&rest
---
http响应头请求头
请求头:
content-type: 请求的数据的类型
Authorization: 身份验证,如JWT这样的
UA: 浏览器环境
响应头:
set-cookie
content-length
cache-control
跨域cookie需要服务器进行设置
---
SEO优化
1. 大量使用语义化标签
2. 保证关键词覆盖率,title之类,配置meta信息
3. 制定一个站点地图,放在“robot.txt”中
4. 结构化数据
+179 -24
View File
@@ -1,37 +1,192 @@
# 以下是关于 ts(以及任何关于 ts 的不限于语法,技巧之类的学习心得) # 以下是关于 ts(以及任何关于 ts 的不限于语法,技巧之类的学习心得)
## any unknown void never ## any unknown void never
- any 表示任意类型。这导致不会对类型进行任何检查,用太多不太好就是了。
- unknown 表示位置类型。看作any的安全版本。可以赋值为任何类型,但在使用之前必须进行类型检查或类型断言。 - any 表示任意类型。这导致不会对类型进行任何检查,用太多不太好就是了。
- void 代表没有返回值。 - unknown 表示位置类型。看作 any 的安全版本。可以赋值为任何类型,但在使用之前必须进行类型检查或类型断言。
- never 代表永远不会有任何返回值,如死循环或抛出异常。 - void 代表没有返回值。
- never 代表永远不会有任何返回值,如死循环或抛出异常。
## 接口 interface ## 接口 interface
- 基本可以认为是声明object类型用的东西
- 基本可以认为是声明 object 类型用的东西
## 泛型 <T> ## 泛型 <T>
- 实际上可以理解为是一种类型的变量 - 实际上可以理解为是一种类型的变量
- 如: - 如:
```ts
function identity<T>(arg: T): T { ```ts
return arg function identity<T>(arg: T): T {
return arg
}
// 使用泛型函数
let result = identity<string>('Hello')
console.log(result) // 输出: Hello
let numberResult = identity<number>(42)
console.log(numberResult) // 输出: 42
// 基本语法
interface Pair<T, U> {
first: T
second: U
}
// 使用泛型接口
let pair: Pair<string, number> = { first: 'hello', second: 42 }
console.log(pair) // 输出: { first: 'hello', second: 42 }
```
## 箭头函数与普通函数的区别
- 箭头函数没有 yield
- 无 prototype
- 没有 this 和 arguments
## 使用 Object.freeze()
- Object.freeze() 可以冻结一个对象,使其不能添加、删除或修改属性。
```ts
const obj = { key: 'value' };
Object.freeze(obj);
obj.key = 'new value'; // 无效
obj.newKey = 'new value'; // 无效
delete obj.key; // 无效
console.log(obj);
```
## 使用 Object.defineProperty()使对象某一 key 其不可变
- Object.defineProperty() 可以定义对象属性,并使其不可变。
```ts
const obj = {};
Object.defineProperty(obj, 'key', {
value: 'value',
writable: false,
configurable: false // 不可配置的
});
obj.key = 'new value'; // 无效
delete obj.key; // 无效
console.log(obj); // 输出: { key: 'value' }
```
## Object.seal()。
- Object.seal() 可以让对象不能添加或删除属性,但允许修改现有属性的值。
```ts
const obj = { key: 'value' };
Object.seal(obj);
obj.key = 'new value'; // 有效
obj.newKey = 'new value'; // 无效
delete obj.key; // 无效
console.log(obj);
```
## 判断是否为数组
- Array.isArray()
- instanceof Array
- obj.**proto** === [].**proto**
## 是否为 NaN
- Number.isNaN()
## obj 可迭代
- 实现了迭代器协议(拥有 Symbol.iterator 方法)实现了迭代器协议(拥有 Symbol.iterator 方法)
```ts
const iterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
} }
}
```
// 使用泛型函数 ## promise新的
let result = identity<string>('Hello')
console.log(result) // 输出: Hello
let numberResult = identity<number>(42) - Promise.race(iterable) 接受一个可迭代对象,返回第一个解决或拒绝的 Promise 的结果。
console.log(numberResult) // 输出: 42 - Promise.allSettled(iterable) 接受一个可迭代对象,返回一个 Promise,所有 Promise 无论解决或拒绝都会返回结果。
- Promise.any(iterable) 接受一个可迭代对象,返回第一个成功解决的 Promise 的结果。如果所有 Promise 都被拒绝,则返回一个拒绝的 Promise。
// 基本语法 ## deepReadonly
interface Pair<T, U> {
first: T ```ts
second: U type DeepReadonly<T> = {
} readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}
```
## callable的对象
```ts
const callableObject = {
[Symbol('callable')](...args) {
console.log('Object called with args:', ...args);
// 执行一些操作并返回结果
return args.reduce((sum, val) => sum + val, 0);
}
}
```
## 异步迭代
```ts
const asyncIterable = {
async *[Symbol.asyncIterator]() {
yield await new Promise(resolve => setTimeout(() => resolve(1), 1000));
yield await new Promise(resolve => setTimeout(() => resolve(2), 1000));
yield await new Promise(resolve => setTimeout(() => resolve(3), 1000));
}
};
(async function() {
for await (const value of asyncIterable) {
console.log(value); // 输出: 1, 2, 3,每个值之间间隔1秒
}
})
```
---
二进制/文件处理
blob: 一种类文件对象,只能使用fileReader进行异步读取,读取为arrayBuffer
arraybuffer: 一种通用的二进制数据缓冲区,需要通过视图(如uint8array)进行修改
---
关于闭包,有逻辑的回答:
闭包(closure)指有权访问另一个函数作用域中变量的函数。 ----- JavaScript 高级程序设计
闭包有什么用?
延伸变量作用域范围,读取函数内部的变量
让这些变量的值始终保持在内存中
简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
---
weakMap
key必须为一个obj
可以用于给一个obj附加一些生命周期相同的数据内容(如缓存)
// 使用泛型接口
let pair: Pair<string, number> = { first: 'hello', second: 42 }
console.log(pair) // 输出: { first: 'hello', second: 42 }
```
+56 -3
View File
@@ -4,7 +4,8 @@
--- ---
# 子子组件之间的通信可以通过事件总线(eventBus)实现 # 子子组件之间的通信可以通过事件总线(eventBus)实现
--- ---
# doucument.createElement出来的东西不及时销毁就会一直游离在外面不被释放,积累下来很不好。
# doucument.createElement出来的东西不及时销毁就会一直游离在外面不被释放,积累下来很不好。(似乎是错误的)
--- ---
@@ -12,7 +13,7 @@
组件设计可以分成业务组件,展示组件,展示组件只负责以对应的形式展示对应的数据,对于业务的处理(如增删查改),则需要通过emit或其他方法交给业务组件处理 组件设计可以分成业务组件,展示组件,展示组件只负责以对应的形式展示对应的数据,对于业务的处理(如增删查改),则需要通过emit或其他方法交给业务组件处理
--- ---
# 路由返回拦截hook # 路由返回拦截hook(路由守卫)
* 当点击返回键(浏览器或者是页面上的路由返回功能)时触发,形式如下: * 当点击返回键(浏览器或者是页面上的路由返回功能)时触发,形式如下:
```ts ```ts
import { onBeforeRouteLeave } from 'vue-router' import { onBeforeRouteLeave } from 'vue-router'
@@ -27,7 +28,7 @@ onBeforeRouteLeave((to, from, next) => {
next(true) next(true)
}) })
``` ```
* 当next(false时,取消路由原有的返回功能) * 当next===false时,取消路由原有的返回功能
--- ---
## 具名作用域插槽 ## 具名作用域插槽
* 具名作用域插槽的工作方式也是类似的,插槽 props 可以作为 v-slot 指令的值被访问到 * 具名作用域插槽的工作方式也是类似的,插槽 props 可以作为 v-slot 指令的值被访问到
@@ -82,4 +83,56 @@ example:
</div> </div>
</template> </template>
</el-popover> </el-popover>
```
# 路由相关:
* 路由守卫
- - beforeEach 全局前置守卫
作用:
主要用于在路由跳转前进行权限验证、页面加载状态的设置等操作。例如,判断用户是否已经登录,如果没有登录且要访问需要登录才能访问的页面,就可以将用户重定向到登录页面。
它可以对每一次路由跳转进行全局拦截,是控制路由访问权限的重要手段。
- - afterEach全局后置守卫
作用:
它在路由跳转完成后被调用,主要用于记录页面访问历史、修改页面标题或者进行一些不需要阻塞路由跳转的操作。
例如,根据不同的路由来更新文档的标题,或者发送页面访问统计数据到服务器。
- - 全局解析守卫(beforeResolve)的触发时机
全局解析守卫beforeResolve在路由导航被触发后,在所有组件内守卫和异步路由组件被解析之前调用。它是在beforeEach之后,但是在组件实例创建和beforeRouteEnter等组件内守卫之前执行。这意味着它处于路由导航过程的一个中间阶段,此时路由已经被匹配,但组件还没有完全解析和进入。
主要作用
数据预获取:
可以用于获取组件渲染所需的数据。与beforeEach不同的是,beforeResolve更专注于确保在组件真正开始渲染之前,所有必要的数据都已经获取并准备好。例如,对于一个包含多个嵌套组件的复杂页面,其中每个组件都需要从服务器获取数据,beforeResolve可以协调这些数据获取操作,确保所有数据都准备好后再进行组件渲染。
处理异步操作的集中点:
它为处理整个路由导航过程中的异步操作提供了一个集中的位置。在大型应用中,可能会有多个异步操作需要在路由跳转时完成,如加载第三方脚本、初始化插件等。beforeResolve可以用于确保这些异步操作都顺利完成后再继续路由解析过程。
- - 组件路由守卫
```vue
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
// 与 beforeRouteLeave 相同,无法访问 `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// 取消导航并停留在同一页面上
if (!answer) return false
})
const userData = ref()
// 与 beforeRouteUpdate 相同,无法访问 `this`
onBeforeRouteUpdate(async (to, from) => {
//仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
</script>
``` ```
+263
View File
@@ -0,0 +1,263 @@
# getUserMedia (获取设备音视频输入)
navigator.mediaDevices.getUserMedia(constraints?: MediaStreamConstraints)
获取用户设备上的输入,比如摄像头,麦克风,返回一个 mediaStream 对象
注意需要通过用户主动操作(如 click)唤起才行,否则会报错。
```ts
const getUserMedia = async (constraints?: MediaStreamConstraints) => {
if (!navigator.mediaDevices.getUserMedia) {
console.log('getUserMedia not supported');
return;
}
mediaStream.value = await navigator.mediaDevices.getUserMedia(constraints);
console.log(mediaStream.value, videoRef.value)
const track = mediaStream.value.getVideoTracks()[0];
console.log(track, track.getSettings())
videoRef.value && (videoRef.value.srcObject = mediaStream.value)
videoRef.value && (videoRef.value.play())
}
```
# getDisplayMedia (获取屏幕音视频输入)
navigator.mediaDevices.getDisplayMedia(constraints?: MediaStreamConstraints)
获取用户屏幕上的音视频,返回一个 mediaStream 对象
注意需要通过用户主动操作(如 click)唤起才行,否则会报错。
```ts
const getDesktopStream = async () => {
if (!navigator.mediaDevices.getDisplayMedia) {
console.log('getDisplayMedia not supported');
return;
}
mediaStream.value = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true,
});
console.log(mediaStream.value, videoRef.value)
videoRef.value && (videoRef.value.srcObject = mediaStream.value)
videoRef.value && (videoRef.value.play())
}
```
# mediaStream
mediaStream 主要表示一系列的音视频轨道
常用:
```ts
mediaStream.addTrack(track);
const tracks = mediaStream.getTracks();
const videoTrack = mediaStream.getVideoTracks()[0];
const audioTrack = mediaStream.getAudioTracks()[0];
```
# mediaStreamTrack
mediaStreamTrack 表示一个音视频轨道
常用属性:
```ts
mediaStreamTrack.id;// 轨道的唯一标识符
mediaStreamTrack.kind;// 轨道的类型,如 video、audio、text 等
mediaStreamTrack.label;// 轨道的标签, 如摄像头名称
mediaStreamTrack.enabled;// 轨道是否可用, false表示不可用, 可能是黑屏等
mediaStreamTrack.muted;// 轨道是否静音
mediaStreamTrack.getSettings();// 获取轨道的设置, 如分辨率、帧率、比特率等
mediaStreamTrack.getConstraints();// 获取轨道的约束,可能是由用户设置的
```
# mediaNegotiate 媒体协商
媒体协商用来交换双端的信息,如可接受的音视频编码等。通过信令服务器交换 sdp 信息。
sdp = Session Description Protocol,webRTC 中分为会话描述和媒体描述
具体流程如下:
1. 客户端 A 通过 getUserMedia 获取音视频输入
2. 客户端 A 通过 createOffer 创建一个 offer
3. 客户端 A 通过 setLocalDescription 设置本地描述
4. 客户端 A 通过 signalingChannel 发送 offer 给客户端 B
5. 客户端 B 通过 setRemoteDescription 设置远程描述
6. 客户端 B 通过 createAnswer 创建一个 answer
7. 客户端 B 通过 setLocalDescription 设置本地描述
8. 客户端 B 通过 signalingChannel 发送 answer 给客户端 A
9. 客户端 A 通过 setRemoteDescription 设置远程描述
10. 客户端 A 和客户端 B 可以开始通话了
代码实现如下:
```ts
// 设置ICE候选,在媒体协商之前
const configuration = {
iceServers: [
{
urls: 'stun:stun.example.com:3478'
},
{
urls: 'turn:turn.example.com:3478',
username: 'your_username',
credential: 'your_password'
}
]
};
// 假设已经有一个信令服务器
const signalingChannel = {...};
const mediaNegotiateA = async () => {
const peerConnection = new RTCPeerConnection(configuration);
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
stream.getTracks().forEach(track => {
peerConnection.addTrack(track, stream);
});
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
console.log('offer', offer);
// 发送offer给B
signalingChannel.send(JSON.stringify(offer));
// 接收B的answer
const answer = await signalingChannel.receive();
console.log('answer', answer);
await peerConnection.setRemoteDescription(answer);
}
const mediaNegotiateB = async () => {
const peerConnection = new RTCPeerConnection(configuration);
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
stream.getTracks().forEach(track => {
peerConnection.addTrack(track, stream);
});
const offer = await signalingChannel.receive();
console.log('offer', offer);
await peerConnection.setRemoteDescription(offer);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
console.log('answer', answer);
// 发送answer给A
signalingChannel.send(JSON.stringify(answer));
}
```
信令服务器只需要负责中继传递 offer 和 answer 即可。
可以使用如 ws 的方式自行实现。
# webRTC 网络穿越
## ICE
交互式连接创建(Interactive Connectivity Establishment,ICE)是一个允许你的浏览器和对端浏览器建立连接的协议框架。在实际的网络当中,有很多原因能导致简单的从 A 端到 B 端直连不能如愿完成。这需要绕过阻止建立连接的防火墙,给你的设备分配一个唯一可见的地址(通常情况下我们的大部分设备没有一个固定的公网地址),如果路由器不允许主机直连,还得通过一台服务器转发数据。
ICE中有控制方(发送offer的)和被控制方,最终地址选择由控制方决定。
具体工作流程如下:
1. 收集候选地址
1. 主机网卡实际地址host
2. 服务器反射候选者srflx(STUN返回的binding success response中的地址,即最外层NAT地址)
3. 中继候选者relay(TURN服务器的地址)
4. 对端反射候选者prflx(对端STUN返回的binding success response中的地址,无法主动获取)
2. 交换候选项
3. 开始连通性检测(一方发送binding request,一方返回binding response)
4. 选择地址从host到srflx/prflx到relay
## NAT 穿透
NAT类型:
完全圆锥型:外部主机可以通过映射后的公网 IP 地址和端口号,主动向内部主机发送数据包。
一旦内部主机的某个私有 IP 地址和端口号被映射到一个公网 IP 地址和端口号,任何外部主机都可以使用这个公网地址和端口号向内部主机发送数据。
受限圆锥型:外部主机只有先收到内部主机发送的数据包后,才能向内部主机发送数据。
具体来说,当内部主机通过 NAT 设备向外部某个主机发送数据包后,NAT 设备会记录这个外部主机的 IP 地址。之后,只有这个被记录的外部主机才能使用 NAT 转换后的公网 IP 地址和端口号向内部主机发送数据。
端口受限圆锥型:比起受限圆锥型,增加了外部主机的端口限制。
对称型:内部主机每次向不同的外部主机发送数据包时,NAT 设备都会为其分配不同的公网 IP 地址和端口号组合。且具有端口受限型的所有特征。
## STUN
STUN(Session Traversal Utilities for NAT,翻译为用于NAT的会话穿越工具)是一种用于 NAT 穿越的协议。它可以帮助 NAT 设备自动发现映射后的公网 IP 地址和端口号。
STUN的工作流程如下:
1. NAT探测阶段:STUN客户端向服务端发送一个binding请求,STUN服务端返回一个binding响应,其中携带MAPPED-ADDRESS、XOR-MAPPED-ADDRESS和RESPONSE-ORIGIN等属性。STUN客户端通过比较各个属性,确定其中是否存在NAT。
2. 打洞阶段:STUN客户端向STUN服务端请求其他STUN客户端的信息,通过交换NAT映射地址尝试实现P2P连接。客户端交替发送绑定请求,在各自的NAT设备上建立会话表项,当两端NAT都建立会话表项的时候,就可以建立P2P连接。
## TURN
TURN(Traversal Using Relays around NAT,翻译为围绕NAT使用中继穿越)是一种用于 NAT 穿越的协议。它可以帮助 NAT 设备通过中继服务器转发数据包,从而实现 NAT 穿越。
## 时机:
设置 ICE 候选人应该在进行媒体协商后进行。
# RTCPeerConnection
表示本地端和远程对等端之间的 WebRTC 连接。它提供了创建远程对等端连接、维护和监视连接,以及在连接不再需要时关闭连接的方法。
主要功能:
```ts
const configuration = {
// 设置ICE服务器
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
},
{
urls: 'turn:your-turn-server-url',
username: 'your-username',
credential: 'your-password'
}
],
// ICE传输策略,可选值有'relay', 'public' 和'all',relay就是只中继
iceTransportPolicy: 'all',
// 当指定当远程对等点与 SDP BUNDLE 标准不兼容时,应如何处理候选的协商。
// balanced
// ICE 代理最初为每一种内容类型(音频、视频、数据通道)创建一个 RTCDtlsTransport。如果远程端点无法感知 BUNDLE,那么每一个 DTLS 传输用于处理一种数据类型的通信。
// max-compat
// ICE 代理最初为每个媒体轨道创建一个 RTCDtlsTransport,对数据通道则创建一个单独的传输。如果远程端点无法感知 BUNDLE,那么对于所有的内容都会协商一个单独的 DTLS 传输。
// max-bundle
// ICE 代理最初仅创建一个 RTCDtlsTransport 来承载所有的 RTCPeerConnection 的数据。如果远程端点无法感知 BUNDLE,那么仅会协商一个轨道而忽略其余的轨道。
bundlePolicy: 'max-bundle',
};
const pc = new RTCPeerConnection(configuration);
// 添加本地音视频流
stream.getTracks().forEach(track => {
pc.addTrack(track, stream);
});
// 设置完本地SDP后会触发
pc.onicecandidate = (event) => {
// 发送ICE候选给对端
signalingChannel.send(JSON.stringify(event.candidate));
};
// 设置远端SDP后会触发
pc.ontrack = (event) => {
// 接收对端的音视频流
videoRef.value.srcObject = event.streams[0];
};
```
+86
View File
@@ -0,0 +1,86 @@
```ts
import {
defineComponent,
watch,
onMounted,
onBeforeUnmount,
h,
getCurrentInstance,
type PropType,
} from 'vue-demi'
import ResizeObserver from './ResizeObserver'
import type { IOptions } from './types'
// const component = {
// name: 'Home',
// props:{
// data: String,
// },
// setup(props, context){
// // props.data
// // context.attrs context.slots context.emit
// }
// }
export default defineComponent({
name: 'ResizeObserverComponent',
props: {
target: [String, Object] as PropType<string | HTMLElement>,
disabled: Boolean as PropType<boolean>,
immediate: Boolean as PropType<boolean>,
delay: {
type: Number as PropType<number>,
default: 150
}
},
setup(props, { slots }) {
let ro: ResizeObserver | null = null
watch(() => props.disabled, (val) => {
if (val) {
ro?.disconnect()
} else {
ro?.observe()
}
})
onMounted(() => {
// const { target, disabled, limiter, wait } = toRefs(props)
// 拿到当前组件的实例(注意已挂载)
const vm = getCurrentInstance()?.proxy
// 先用$el
let $el: HTMLElement = vm?.$el as HTMLElement
if (props.target) {
if (typeof props.target === 'string') {
$el = document.querySelector(props.target) as HTMLElement
} else if (props.target instanceof HTMLElement) {
$el = props.target
}
}
const options: IOptions = {
delay: props.delay,
immediate: props.immediate,
resize: (data, elem) => {
vm?.$emit('resize', data, elem)
}
}
ro = new ResizeObserver($el, options)
})
// 销毁 ResizeObserver实例
onBeforeUnmount(() => {
if (ro) {
ro.destroy()
}
})
// 如果有单个子元素则直接渲染子元素
const defaultContent = slots.default?.()
return () => (
(defaultContent && defaultContent.length === 1)
? defaultContent[0]
: h('div', defaultContent)
)
}
})
```
View File
Whitespace-only changes.