uniapp 微信小程序 提词器(纯手工)

功能:在小程序中调取手机相机录制视频,录制过程中提词器中字体跟着滚动,提词器颜色改变。

效果展示

提词器不显示:

修改提词器样式:

内容编写:

提词器下拉增加高,提词器可上下拖动:

开始录制(再次点击接受录制返回视频路径):

退出录制:

以下粘贴为全部代码,即拿即用:

<template>
	<view class="box">
		<view class="fixedTop">
			<view
			  class="tui-status-bar"
			  :style="{ height: statusBarHeight + 'px' }"
			></view>
			<view class="btn" :style="{height:height+'px'}">
				<image src="https://doctor.sdxxtop.com/img/tools/teleprompterClone.png" @click="showBackPopUp = !showBackPopUp"></image>
				<image v-if="showTextPopUp" src="https://doctor.sdxxtop.com/img/tools/teleprompterShow.png" @click="showTextPopUp = false"></image>
				<image v-else src="https://doctor.sdxxtop.com/img/tools/teleprompterUnShow.png" @click="showTextPopUp = true"></image>
			</view>
			<view class="textPopUp" v-if="showTextPopUp" :style="'margin-top:'+dragPopUpHeight+'px'" @touchmove="handleTouchMovePopUp" @touchstart="touchstartPopUp($event)">
				<view class="text" :style="{ height: dragHeight + 'px',color:textColor }">
					<scroll-view scroll-y :style="{ height: dragHeight + 'px' }" lower-threshold="0" :scroll-top="scrollTop" @scrolltolower="scrollBottom">
						{{text?text:'你好,欢迎来到医生医影提词器,已为你开启智能跟随模式,台词跟着语速走,念到哪走到哪,节奏由你掌控;点击左下角设置按钮,可以修改台词样式配快来尝试录制示下吧~'}}
					</scroll-view>
				</view>
				<view class="textBtn">
					<image @click="teleprompterSet" src="https://doctor.sdxxtop.com/img/tools/teleprompterSet.png"></image>
					<image @click="teleprompterLog" src="https://doctor.sdxxtop.com/img/tools/teleprompterLog.png"></image>
					<image @touchmove.stop="handleTouchMove" @touchstart="touchstart($event)" src="https://doctor.sdxxtop.com/img/tools/teleprompterZoom.png"></image>
				</view>
			</view>
			<view class="backPopUp" v-if="showBackPopUp">
				<text></text>
				<view @click="quit">
					<image src="https://doctor.sdxxtop.com/img/tools/teleprompterBack.png"></image>
					<text>退出拍摄页</text>
				</view>
				<view @click="againTake">
					<image src="https://doctor.sdxxtop.com/img/tools/teleprompterAgain.png"></image>
					<text>重新拍摄</text>
				</view>
			</view>
		</view>
		<view class="content">
			<camera :device-position="devicePosition ? 'back' : 'front'" flash="off" @error="loadCameraError" :style="{width: '100%', height: winHeight+'px'}"></camera>
		</view>
		<!-- 开始录制 -->
		<view class="start">
			<image src="https://doctor.sdxxtop.com/img/tools/teleprompterStart.png" v-if="!isStartRecord" @click="startVideotape"></image>
			<image src="https://doctor.sdxxtop.com/img/tools/teleprompterStop.png" v-else @click="stopVideoTape"></image>
			<view class="turn" @click="turnCamera" v-if="!isStartRecord">
				<image src="https://doctor.sdxxtop.com/img/tools/teleprompterTurn.png"></image>
				<view>翻转</view>
			</view>
			<view class="time" v-if="isStartRecord">{{formatSeconds(recordTime)}}</view>
		</view>
		<!-- 蒙层 -->
		<view class="mongolian" v-if="mongolian">
			<view class="colorBox">
				<view v-for="(item,index) in 9" :class="chooseColorIndex == index ? 'chooseColor' : '' " @click="chooseColor(index)"></view>
			</view>
		</view>
		<!-- 修改编写 -->
		<view class="editText" v-if="editTextPopUp" :style="{ marginTop : ( statusBarHeight + 44 ) +'px' }">
			<view class="editTextTitle">
				<text @click="cancelText">取消</text>
				<text>修改编写</text>
				<text @click="confirmText">确认</text>
			</view>
			<view class="editTextContent">
				<textarea maxlength="-1" v-model="text" :style="'height:'+(winHeight-200)+'px;'" placeholder="请输入或粘贴"></textarea>
			</view>
		</view>
	</view>
</template>

<script>
	import { Debounce } from "@/utils/debounce.js";
	export default {
		data(){
			return {
				// 手机高度
				winHeight: 0,
				//状态栏高度
				statusBarHeight: 0, 
				//header高度
				height: 44, 
				// 显示提词器弹窗
				showTextPopUp:true,
				// 触摸开始的Y坐标
				startY: 0, 
				// 拖动区域的高度
				dragHeight: 150, 
				// 最大高度限制
				maxHeight: 450 ,
				// 整个弹窗触摸开始y的坐标
				startPopUpY:0,
				// 拖动区域的高度
				dragPopUpHeight: 0, 
				// 选中颜色下标
				chooseColorIndex:0,
				// 动态字体颜色
				textColor:"#FFFFFF",
				// 是否显示蒙层
				mongolian:false,
				// 内容
				text:"",
				// 编辑文字弹窗
				editTextPopUp:false,
				// 相机内置
				ctx:null,
				// 录制生成的视频
				createVideoUrl:'https://yishivideos.bj.bcebos.com/jinan_demo/video/20240328/d8a426d9a9ef37b2a28040fbcd470f28.mp4',
				// 是否开始录制
				isStartRecord:false,
				// 前后置摄像头  true:前置 false:后置
				devicePosition:true,
				// 录制时长
				recordTime:0,
				// 录制时长定时器
				recordTimeFun:null,
				// 显示返回弹窗
				showBackPopUp:false,
				// 提词器移动定时事件
				scrollTopTimeFun:null,
				// 滚动条位置
				scrollTop:0
			}
		},
		created() {
			uni.getSystemInfo({
			  success: (res) => {
			    this.statusBarHeight = res.statusBarHeight
			  }
			})
		},
		onLoad(option) {
			var that = this;
			if(option.text){
				this.text = option.text
			}
			uni.getSystemInfo({
				success: function(res) {
					that.winHeight = res.windowHeight
				},
			});
		},
		onReady() {
			this.ctx = uni.createCameraContext();
		},
		methods:{
			// 移动到底部
			scrollBottom(e){
				clearInterval(this.scrollTopTimeFun)
			},
			// 重新拍摄
			againTake(){
				this.text = '';
				this.isStartRecord = false;
				this.showBackPopUp = false;
				this.scrollTopTimeFun = null;
				
			},
			// 退出
			quit(){
				uni.navigateBack()
			},
			// 处理时分秒
			formatSeconds(seconds) {
			    seconds = Math.floor(seconds);
			    var hours = Math.floor(seconds / 3600);
			    var minutes = Math.floor((seconds - (hours * 3600)) / 60);
			    var secs = seconds % 60;
			 
			    return [ minutes, secs ].map(num => num < 10 ? '0' + num : num).join(':');
			},
			// 取消文字
			cancelText(){
				this.editTextPopUp = false
				this.text = ''
			},
			// 确认文字
			confirmText(){
				this.editTextPopUp = false
			},
			// 切换颜色
			chooseColor(index){
				this.chooseColorIndex = index
				if(index == 0){
					this.textColor = '#FFFFFF'
				}else if(index == 1){
					this.textColor = '#424242'
				}else if(index == 2){
					this.textColor = '#000000'
				}else if(index == 3){
					this.textColor = '#FA797D'
				}else if(index == 4){
					this.textColor = '#FB6F24'
				}else if(index == 5){
					this.textColor = '#F9D63C'
				}else if(index == 6){
					this.textColor = '#77E46F'
				}else if(index == 7){
					this.textColor = '#5A95EF'
				}else if(index == 8){
					this.textColor = '#998BFC'
				}
			},
			// 提词器设置
			teleprompterSet(){
				this.mongolian = !this.mongolian
			},
			// 编辑文字
			teleprompterLog(){
				this.editTextPopUp = !this.editTextPopUp
			},
			// 移动整个弹窗起始点
			touchstartPopUp(e){
				this.startPopUpY = e.changedTouches[0].clientY
			},
			// 移动整个弹窗
			handleTouchMovePopUp(event){
				// 获取触摸点信息
				const touch = event.touches[0];
				if (this.startPopUpY === 0) {
					this.startPopUpY = touch.clientY;
				} else {
					// 计算移动的距离
					const diff = touch.clientY - this.startPopUpY;
					// 更新拖动区域高度
					if(this.dragPopUpHeight + diff >= 300){
						this.dragPopUpHeight = 300
						this.startPopUpY = 0
						return
					}
					if( diff > 0 ){
						this.dragPopUpHeight = Math.max(this.dragPopUpHeight, this.dragPopUpHeight + diff);
					}else{
						this.dragPopUpHeight = Math.max(0, this.dragPopUpHeight + diff);
					}
					// 重置起始Y坐标
					this.startPopUpY = touch.clientY;
				}
			},
			//开始触摸时
			touchstart(e) {
			  this.startY = e.changedTouches[0].clientY
			},
			// 触摸移动
			handleTouchMove(event){
				// 获取触摸点信息
				const touch = event.touches[0];
				// 如果是第一次触摸
				if (this.startY === 0) {
					this.startY = touch.clientY;
				} else {
					// 计算移动的距离
					const diff = touch.clientY - this.startY;
					// 更新拖动区域高度
					if(this.dragHeight + diff < 150){
						this.dragHeight = 150
						this.startY = 0
						return
					}
					this.dragHeight = Math.min(this.maxHeight, this.dragHeight + diff);
					// 重置起始Y坐标
					this.startY = touch.clientY ;
				}
			},
			// 防抖
			Debounce: Debounce(function(e) {
				var that = this;
				if(e == 1){
					that.scrollTopTimeFun = setInterval(()=>{
						this.scrollTop++;
					},80)
					this.ctx.startRecord({
						timeout:300,
						success: (res) => {
							that.isStartRecord = true
							that.recordTimeFun = setInterval(()=>{
								that.recordTime++
							},1000)
							console.log(res,"开始")
						},
					});
				}else{
					clearInterval(that.recordTimeFun)
					clearInterval(that.scrollTopTimeFun)
					uni.showLoading({
						title:"上传中..."
					})
					that.isStartRecord = false
					that.recordTime = 0
					this.ctx.stopRecord({
						success:(res=>{
							uni.uploadFile({
							  url: 'https://xxx/admin/index/uploadVideo',
							  name: 'file',
							  header: {},
							  formData: {},
							  filePath: res.tempVideoPath,
							  success(res) {
								  var d = JSON.parse(res.data.replace(/\ufeff/g, '') || '{}');
								  that.createVideoUrl = d.data.url
								  uni.hideLoading()
								  uni.navigateTo({
								  	url: '/pages/teleprompter/playVideo?url='+that.createVideoUrl
								  })
							  },
							})
						})
					})
				}
			}, 1500),
			// 开始录制
			startVideotape(){
				this.Debounce(1)
			},
			// 暂停录制
			stopVideoTape(){
				this.Debounce(2)
			},
			// 翻转相机
			turnCamera(){
				this.devicePosition = !this.devicePosition
			},
			// 加载相机错误
			loadCameraError(e) {
				console.log(e.detail);
			}
		}
	}
</script>

<style lang="scss" scoped>
	.box{
		position: fixed;
		top: 0;
		left: 0;
		bottom: 0;
		right: 0;
		.fixedTop{
			background-color: black;
			position: fixed;
			width: 100%;
			top: 0;
			left: 0;
			z-index: 123;
			.tui-status-bar {
			  width: 100%;
			}
			.btn{
				padding-right: 250rpx;
				display: flex;
				align-items: center;
				justify-content: space-between;
				padding-left: 30rpx;
				image{
					width: 50rpx;
					height: 50rpx;
				}
			}
			.textPopUp{
				position: absolute;
				top:100%;
				left: 50%;
				z-index: 123;
				width: 90%;
				box-sizing: border-box;
				background-color: #00000050;
				padding: 30rpx;
				border-radius: 20rpx;
				transform: translateX(-50%);
				.text{
					font-size: 35rpx;
					color: #FFFFFF;
					line-height: 63rpx;
					overflow: hidden;
				}
				.textBtn{
					margin-top: 60rpx;
					display: flex;
					align-items: center;
					justify-content: space-between;
					image{
						width: 43rpx;
						height: 43rpx;
					}
					image:nth-of-type(1){
						height: 36rpx
					}
					image:nth-of-type(2){
						width: 35rpx;
						height: 35rpx;
					}
				}
			}
			.backPopUp{
				position: absolute;
				top: 115%;
				left: 20rpx;
				z-index: 1111;
				background-color: white;
				border-radius: 20rpx;
				font-size: 28rpx;
				&>text{
					border-color: #ff450000 #ff450000 white #ff450000;
					display: inline-block;
					width: 0;
					height: 0;
					border-width: 20rpx;
					border-style: solid;
					position: absolute;
					top: 0%;
					transform: translateY(-90%);
					left: 15rpx;
				}
				&>view{
					padding: 30rpx;
					display: flex;
					align-items: center;
					&>image{
						width: 32rpx;
						height: 32rpx;
						margin-right: 20rpx;
					}
				}
				&>view:nth-of-type(1){
					border-bottom: 2rpx solid #EFEFEF;
					color: #621AA4;
					&>image{
						width: 40rpx;
						height: 32rpx;
					}
				}
			}
		}
		.start{
			position: fixed;
			bottom: 180rpx;
			left: 50%;
			transform: translateX(-50%);
			display: flex;
			align-items: center;
			
			&>image{
				width: 120rpx;
				height: 120rpx;
			}
			.turn{
				margin-left: 80rpx;
				text-align: center;
				image{
					height: 35rpx;
					width: 42rpx;
				}
				font-size: 28rpx;
				color: #FFFFFF;
			}
			.time{
				font-size: 38rpx;
				color: #FFFFFF;
				position: absolute;
				top: -80%;
				left: 50%;
				transform: translateX(-50%);
			}
		}
		.mongolian{
			position: fixed;
			top: 0;
			left: 0;
			right: 0;
			bottom: 0;
			background-color: #000000;
			.colorBox{
				position: absolute;
				bottom: 200rpx;
				left: 50%;
				transform: translateX(-50%);
				display: flex;
				align-items: center;
				justify-items: center;
				.chooseColor{
					border: 2rpx solid white;
				}
				view{
					width: 63rpx;
					height: 63rpx;
				}
				view:nth-of-type(1){
					background-color: white;
				}
				view:nth-of-type(2){
					background-color: #424242;
				}
				view:nth-of-type(3){
					background-color: #000000;
				}
				view:nth-of-type(4){
					background-color: #FA797D;
				}
				view:nth-of-type(5){
					background-color: #FB6F24;
				}
				view:nth-of-type(6){
					background-color: #F9D63C;
				}
				view:nth-of-type(7){
					background-color: #77E46F;
				}
				view:nth-of-type(8){
					background-color: #5A95EF;
				}
				view:nth-of-type(9){
					background-color: #998BFC;
				}
			}
		}
		.editText{
			position: fixed;
			top: 0;
			left: 0;
			right: 0;
			bottom: 0;
			background-color: white;
			z-index: 123;
			border-radius: 20rpx 20rpx 0 0;
			.editTextTitle{
				display: flex;
				align-items: center;
				justify-content: space-between;
				padding: 30rpx 20rpx;
				border-bottom: 2rpx solid #D8D8D8;
				text:nth-of-type(1){
					font-size: 28rpx;
					color: #666666;
				}
				text:nth-of-type(2){
					font-size: 32rpx;
				}
				text:nth-of-type(3){
					font-size: 28rpx;
					color: #621AA4;
				}
			}
			.editTextContent{
				padding: 20rpx;
				textarea{
					width: 100%;
				}
			}
		}
	}
</style>

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值