在线录屏-轻松完成教程演示、产品演示、用户支持、错误排查和用户体验研究等用途.

avatar
cmdragon 渡劫

在线录屏是指在互联网上进行屏幕录制的过程。它允许用户通过网络连接,将自己的屏幕活动记录下来,并可以在需要时进行播放、共享或存档。在线录屏常用于教育、培训、演示、游戏等场景,可以帮助用户展示操作步骤、解决问题、分享经验等。通常,在线录屏工具提供了丰富的功能,例如选择录制区域、添加音频注释、调整录制质量等,以满足用户的不同需求。

在线体验地址:https://amd794.com/recordscreen

工具演示视频:https://www.bilibili.com/video/BV1wC4y1U7at/

相关说明:

MediaDevices 是 Web API 中的一部分,它提供了访问媒体设备(如摄像头、麦克风等)的功能。通过 MediaDevices 接口,您可以使用 JavaScript 代码来获取和操作媒体设备的流(如音频和视频)。这使得您可以在 Web 应用程序中实现音视频通信、媒体录制和流媒体等功能。

当使用 MediaDevices 接口调用 Web API 时,可以按照以下示例代码进行操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 检查浏览器是否支持 MediaDevices 接口
if (navigator.mediaDevices) {
// 获取媒体设备的用户授权
navigator.mediaDevices.getUserMedia({ audio: true, video: true })
.then(function(stream) {
// 获取到媒体流后的处理逻辑
// 在这里可以对获取到的音视频流进行操作
// 例如:显示视频流、播放音频流等
})
.catch(function(error) {
// 处理获取媒体设备失败的情况
console.log('获取媒体设备失败: ' + error.message);
});
} else {
console.log('您的浏览器不支持 MediaDevices 接口');
}

上述示例代码首先检查浏览器是否支持 MediaDevices 接口。如果支持,它会调用 getUserMedia 方法来请求用户授权访问媒体设备(包括音频和视频)。如果用户授权成功,将返回一个媒体流对象,可以在 .then 方法中对该流进行处理。如果用户拒绝授权或发生其他错误,将在 .catch 方法中处理错误情况。

通过这样的调用方式,您可以使用 MediaDevices 接口来获取媒体设备的流,并对其进行进一步的操作和处理。

浏览器兼容性:

可以说是大部分不支持,毕竟是新的Web API接口,详细可以去MDN中查看MediaDevices - Web API 接口参考 | MDN (mozilla.org)

image

image

image

相关实现代码:

潦草布局一下,毕竟只是个玩具,不需要多华丽。

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
<div class="RecordScreen">
<div class="Operate">
<div class="Content">
<div class="flex Start" @click="onStart" v-if="!setState">
<el-icon size="50px" color=" var(--el-color-primary)">
<VideoCameraFilled/>
</el-icon>
<el-button type="primary">{{ currentLang.start }}</el-button>
</div>
<div class="flex Pause" @click="onPause" v-if="setState === 'recording'">
<el-icon size="50px" color=" var(--el-color-primary)">
<VideoPause/>
</el-icon>
<el-button type="info">{{ currentLang.stop }}</el-button>
</div>
<div class="flex Resume" @click="onResume" v-if="setState === 'paused'">
<el-icon size="50px" color=" var(--el-color-primary)">
<VideoPlay/>
</el-icon>
<el-button type="info">{{ currentLang.continue }}</el-button>
</div>
<div class="flex Stop" @click="onStop" v-if="setState">
<el-icon size="50px" color=" var(--el-color-primary)">
<SwitchButton/>
</el-icon>
<el-button type="danger">{{ currentLang.end }}</el-button>
</div>
</div>
<el-divider v-if="VideoURL"/>
<div class="Operate__Download">
<div v-if="!VideoURL"
style="width: 100vw;height: 100vh;position: absolute;top: 0;left: 0;z-index: 99999;background-color: var(--el-bg-color);">
</div>
<el-input v-model="DownloadName" placeholder="please input">
<template #append>.mp4</template>
</el-input>
<el-button type="primary" :icon="Download" @click="onDownload">{{ currentLang.saveVideo }}</el-button>
</div>
</div>
<div class="RecordScreen__state">
<div v-if="setState" class="REC">
<div></div>
<div>REC</div>
</div>
<el-icon v-if="!VideoURL" size="40vh" color="var(--el-color-primary)">
<Monitor/>
</el-icon>
<div v-if="setState" class="Timing">
<div>{{ Hour }}:{{ Minute }}:{{ Seconds }}</div>
<div>{{ setState === 'paused' ? currentLang.pauseScreenRecording : currentLang.recordingScreen }}</div>
</div>
<video v-if="VideoURL" :src="VideoURL" controls></video>
</div>
</div>

简单定义几个变量

1
2
3
4
5
const VideoURL = ref('')
const DownloadName = ref('')
const setState = ref('')
let mediaRecorder = null
let mediaThen = null

开始录屏

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
const onStart = () => {
VideoURL.value = ''
mediaThen = null
navigator.mediaDevices.getDisplayMedia({video: true, audio: true})
.then(mediaStream => {
mediaThen = mediaStream
mediaRecorder = new MediaRecorder(mediaStream);
Timing()
console.log(mediaRecorder)
const chunks = [];
mediaRecorder.ondataavailable = event => {
if (event.data.size > 0) {
chunks.push(event.data);
}
};
mediaRecorder.onstop = () => {
const blob = new Blob(chunks, {type: 'video/mp4'});
const recordedVideoURL = URL.createObjectURL(blob);
VideoURL.value = recordedVideoURL
setState.value = '' //inactive
};
mediaRecorder.start();
setState.value = mediaRecorder.state //recording


})
.catch(error => {
// if (error == 'DOMException: Permission denied') return false
// ElMessageBox({
// title: '当前浏览器或设备不支持录屏',
// confirmButtonText: '确定',
// message: h('p', null, [
// h('span', null, error),
// ]),
// })
});
}

暂停录屏

1
2
3
4
5
const onPause = () => {
clearTimeout(timer)
mediaRecorder.pause()
setState.value = mediaRecorder.state //paused
}

恢复录屏

1
2
3
4
5
const onResume = () => {
Timing()
mediaRecorder.resume()
setState.value = mediaRecorder.state //recording
}

结束录屏

1
2
3
4
5
const onStop = () => {
mediaRecorder.stop()
mediaThen.getTracks().forEach(track => track.stop())
setState.value = '' //inactive
}

最后把样式补上

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
<style scoped lang="scss">
@mixin Flex {
display: flex;
align-items: center;
justify-content: center;
}

@mixin side {
padding: 10px;
gap: 10px;
}


@mixin font($size: 0.9rem, $Color: red, $spacing: 0.2rem) {
font-size: $size;
line-height: $size;
color: $Color;
letter-spacing: $spacing;
}


.RecordScreen {
@font-face {
font-family: digital-display;
src: url(data:font/woff2;base64,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)
}

$bg: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSLlm77lsYJfMSIgeD0iMHB4IgogICAgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMzQ2IDE0NyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzQ2IDE0NzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgogICAgPGcgc3R5bGU9Im9wYWNpdHk6IDAuMSI+CiAgICAgIDxwYXRoIHN0eWxlPSJmaWxsOiAjN0NGMUZEOyIKICAgICAgICBkPSJNMzM2LDg1LjNjLTg3LjktMS41LTcxLjEtNTguMS05MC43LTU3LjhjLTEzLjgsMC4yLTEzLjYsMzkuNi0zNSwzOC45QzE4NS4yLDY1LjYsMTkyLjcsMCwxNzAuMSwwICAgYy0yMi45LDAtMTYuOCw3NS4yLTQ4LjcsNzQuMmMtMzAuMi0wLjgtMzAuMS0zNC4zLTM3LjUtMzQuM2MtNy4zLDAtOC44LDQyLjUtMzUsNDIuNWMtMTguMywwLTE3LjMtMTcuNC0yNi43LTE3LjQgICBDMTMuOCw2NS4xLDE0LjUsODAsMCw4NC40djAuNWgyOThoMzhWODUuM3oiIC8+CiAgICAgIDxwYXRoIHN0eWxlPSJmaWxsOiAjMDA4NEZGOyIKICAgICAgICBkPSJNMzQyLjIsODUuM2MtMzQuMSwwLTM0LjIsMjItNDguNSwyMi4yYy0xMS4zLDAuMi0xNC0xOS40LTM5LjctMTguOWMtMzMuOSwwLjctMjYuNiw1OC40LTQ2LjMsNTguNCAgIGMtMTguOSwwLTEyLjgtNTkuMS00NC4zLTU5LjFjLTMyLjYsMC0yNS4yLDM5LjYtMzcsMzkuNmMtMTAuNSwwLTUuNS0zOS40LTM2LjItMzkuOWMtMjUuOS0wLjMtMjkuOSwxMC4xLTQyLjcsOS45ICAgQzMwLjEsOTcuMywzMS4yLDg1LjEsNi41LDg1LjFoMzM1LjdWODUuM3oiIC8+CiAgICAgIDxwYXRoIHN0eWxlPSJvcGFjaXR5OiAwLjU7ZmlsbDogIzAwRUFGRjtlbmFibGUtYmFja2dyb3VuZDogbmV3OyIKICAgICAgICBkPSJNMzQ2LDg1LjNjLTM4LjcsMC00My41LTE1LjktNTYuMy0xNi4xYy0xMC0wLjItMTQsMTAuMS00NC4yLDkuNGMtMzQuMS0wLjctMjMuNC00MC40LTQwLjktNDAuNCAgIGMtMTYuNiwwLTguNSw0Mi0zNi4yLDQxLjlDMTQ2LDgwLDE1MC41LDUyLjgsMTM2LjIsNTIuOGMtMTMuMywwLTEzLjgsMzAuMy0zMy45LDMwLjZDODUsODMuNiw3NS43LDY3LDY1LjYsNjcgICBjLTEyLjEsMC0xNi4zLDE4LjItMzkuNSwxOC4ySDM0NnoiIC8+CiAgICA8L2c+CiAgPC9zdmc+';


@include Flex;
justify-content: space-around;
height: 100vh;
overflow: hidden;
box-shadow: var(--el-box-shadow);
position: relative;
flex-direction: column-reverse;

.flex {
@include Flex;
flex-direction: column;
}

.Operate {
.Content {
@include Flex;

.Start {
@include side;
}

.Pause {
@include side;
}

.Resume {
@include side;
}

.Stop {
@include side;
}
}

&__Download {
@include Flex;
gap: 10px;
position: relative;
overflow: hidden;
}
}

&__state {
position: relative;
border-radius: 1rem;
@include Flex;
justify-content: space-around;
width: 57vmax;
height: 70vh;
border: 1px dashed var(--el-color-primary);
background: #022125 url($bg) center no-repeat;
overflow: hidden;

.REC {
@include Flex;
gap: 0.5rem;
position: absolute;
top: 0.9rem;
left: 0.9rem;

:first-child {
width: 1rem;
height: 1rem;
border-radius: 50rem;
background-color: red;
}

:last-child {
@include font()
}
}


.Timing {
display: flex;
align-items: flex-start;
gap: 1rem;
flex-direction: column;

:first-child {
@include font(2rem, #1fb5c9, 0.3rem);
font-weight: 600;
font-family: digital-display;
}

:last-child {
@include font(1.8rem, #1fb5c9, 0.3rem);
font-weight: 600;
}
}

video {
width: 100%;
height: 100%;
vertical-align: middle;

}
}
}
</style>