微信小程序开发之——仿微信视频录制上传

一 仿微信视频录制效果

二 业务说明

  1. 视频录制前,只显示取消和视频录制按钮
  2. 录制开始后,只显示视频录制按钮,并且上方显示时间、录制按钮显示录制进度
  3. 录制完成后,显示重拍、取消、上传按钮
  4. 录制完成后,点击按钮,可预览录制视频
  5. 点击重拍,录制视频消失,显示相机预览画面
  6. 点击取消,返回上一个界面
  7. 点击上传,将视频上传

三 可能遇到的问题(显示录制倒计时和录制进度)

3.1 布局文件(canvas绘制)

1
2
3
4
5
6
7
8
9
10
11
12
 <!--底部布局-中间部分-canvas绘制-->
<view>
<text class="delay-time" hidden="{{!isRecord||isFinish}}">{{timeDelay}}</text>
<view class="container-record" style="width: 200rpx;height: 200rpx;">
<view class='video-button-container' bindlongpress="startRecordLong" bindtouchend="bindtouchend" hidden="{{!initState||isFinish}}">
<canvas canvas-id="circleOuterLayer" style="position: absolute;margin: auto 0;width: 200rpx;height: 200rpx;"></canvas>
<canvas canvas-id="circleInnerLayer" style="position: absolute;margin: auto 0;width: 200rpx;height: 200rpx;" hidden="{{isRecord||(!isRecord&&isFinish)}}"> </canvas>
<canvas canvas-id="circleRectLayer" style="position: absolute;margin: auto 0;width: 200rpx;height: 200rpx;" hidden="{{!isRecord}}"> </canvas>
<canvas canvas-id="circleProgressLayer" style="position: absolute;margin: auto 0;width: 200rpx;height: 200rpx;"> </canvas>
</view>
</view>
</view>

3.2 样式文件

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
.container-camera {
width: 100%;
height: 100%;
text-align: center;
display: flex;
justify-content: center;
align-items: center;

}

/**相机布局 */
.container-camera camera {
width: 100vh;
height: 100vh;

}

/**视频回放布局 */
.container-camera video {
width: 100vh;
height: 100vh;
}

.container-bottom {
position: absolute;
bottom: 40rpx;
width: 90%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
z-index: 100;
}

.retake {
position: absolute;
bottom: 220rpx;
left: 20px;
color: white;
background: #7E91AD;
padding: 20rpx 50rpx;
border-radius: 20rpx;

}

.container-bottom-left {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-end;
}

.container-bottom-left view {
background: #7E91AD;
padding: 20rpx 50rpx;
border-radius: 20rpx;
color: white;

}

.container-bottom-left view:first-child {
margin-bottom: 20rpx;
/* margin-top: -60rpx; */
/* position: absolute;
margin-bottom: 140rpx; */
}

.container-bottom-right {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-end;
}

.container-bottom-right view {
background: #377EF0;
padding: 20rpx 50rpx;
border-radius: 20rpx;
color: white;
}


/**最外层布局 */
.container-record {

width: 200rpx;
height: 200rpx;

}

.video-button-container {
width: 200rpx;
height: 200rpx;
position: relative;
display: flex;
justify-content: center;
align-items: center;

}

3.3 逻辑文件

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
  /**
* 页面的初始数据
*/
data: {
position: 'back', //摄像头的位置,有前置和后置
timeDelay: "15秒", //视频最大时长
countTimer: null, // 设置 定时器 初始为null
intervalNum: 15, //视频最大时长倒计时
initState: true, //初始化状态
isRecord: false, //是否正在录制
isFinish: false, //是否录制完成
startRecord: false, //是否开始录像,控制录像的默认状态,
videoSrc: '' //录像文件的位置
},

/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {

//1-内层实体圆-白色-默认状态显示
var circleInnerCtx = wx.createCanvasContext('circleInnerLayer')

// 绘制图形
circleInnerCtx.arc(50, 50, 30, 0, 2 * Math.PI)
circleInnerCtx.setFillStyle('#EEEEEE')
circleInnerCtx.fill()
circleInnerCtx.draw()

//2-外层圆圈-白色-默认状态显示
var circleOuterCtx = wx.createCanvasContext('circleOuterLayer')

// 绘制图形
circleOuterCtx.arc(50, 50, 40, 0, 2 * Math.PI)
circleOuterCtx.setStrokeStyle('#EEEEEE')
circleOuterCtx.setLineWidth(5)
circleOuterCtx.stroke()
circleOuterCtx.draw()

//3-正方形-红色-录制视频时显示-与2不可能共存
var context = wx.createCanvasContext('circleRectLayer')

// context.fillRect(30, 30, 40, 40)
const pi = Math.PI;
const left = 30;
const top = 30;
const r = 10;
const width = 40;
const height = 40;
context.beginPath();
context.arc(left + r, top + r, r, -pi, -pi / 2);
context.arc(left + width - r, top + r, r, -pi / 2, 0);
context.arc(left + width - r, top + height - r, r, 0, pi / 2);
context.arc(left + r, top + height - r, r, pi / 2, pi);
context.closePath();
context.setFillStyle('red')
context.fill()
context.draw()
//4-绘制圆
//5-倒计时
},

/**录制视频时,外圈显示录制进度 */
recordingCanvas() {
this.setData({
isRecord: true,
})
// debugger
var totalTime = this.data.intervalNum;

this.data.countTimer = setInterval(() => {

if (totalTime > 0) {
//num++
totalTime--;
this.setData({
timeDelay: totalTime + "秒"
})

//context.moveTo(0,0)
//cxtProgress = wx.createCanvasContext('circleProgressLayer')
// context.rotate(-1 / 2 * Math.PI / 180);
cxtProgress.beginPath();
cxtProgress.arc(50, 50, 40, -Math.PI / 2, 2 * Math.PI * ((15 - totalTime) / 15) - Math.PI / 2)
//cxtProgress.arc(50, 50, 40, -1 / 2 * Math.PI, 3 / 2 * Math.PI * ((15 - totalTime) / 15))
cxtProgress.setStrokeStyle('red')
cxtProgress.setLineWidth(5)
cxtProgress.stroke()
cxtProgress.draw()
} else {
clearInterval(this.data.countTimer)
this.bindtouchend();
}
}, 1000)
},
bindtouchend(options) {
console.log("--bindtouchend");
if (this.data.isRecord) //已经开始录制视频时
{
clearInterval(this.data.countTimer) //清除计数器
this.clearCanvasRect(); //清除Canvas绘制的内容
this.stopRecord(); //停止录像
this.setData({
isFinish: true,
isRecord: false
})
},
//清除Canvas绘制的内容
clearCanvasRect() {
//清除画布上的所有内容
cxtProgress.clearRect(0, 0, wx.getSystemInfoSync().windowWidth, wx.getSystemInfoSync().windowHeight)
cxtProgress.draw()
},