im/public/h5/hybrid/html/index.html

614 lines
18 KiB
HTML
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<html lang="zh">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<script type="text/javascript" src="./rtc/adapter-latest.js"></script>
<script type="text/javascript" src='./js/uni.webview.js'></script>
<script type="text/javascript" src='./js/utils.js'></script>
<script type="text/javascript" src='./js/jsonly.js'></script>
<style>
body{
padding:0;
margin:0;
background-image: url('image/wallpaper.png');
background-size: contain;
}
.webrtc-box{
background: #666;
border-radius: 6px;
width:100%;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.localvideo{
width:100vw;
height:100vh;
object-fit: cover;
}
.remotevideo{
min-height: 160px;
width: 100px;
position: fixed;
top: 40px;
right: 15px;
z-index:10;
object-fit: cover;
}
.call-user-box{
position:fixed;
bottom: 20px;
width:100%;
}
.call-user{
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-bottom:50px;
}
.call-user .avatar{
width:60px;
height:60px;
object-fit: contain;
border-radius: 50%;
overflow: hidden;
}
.call-user .text{
font-size:16px;
margin-top:15px;
color:#f6f6f6
}
.call-time{
color:#f6f6f6;
font-size: 24px;
text-align: center;
}
.calling-button{
display: flex;
justify-content: space-around;
padding: 20px;
}
.calling-button .button{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.calling-button .button .image{
width:60px;
height:60px;
margin-bottom: 10px;
}
.calling-button .button .text{
color:#f6f6f6;
}
.calling-button .switch-btn .text{
font-size:12px !important;
}
.calling-button .button .image-icon{
width:40px;
height:40px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div id="app">
<div class="webrtc-box">
<audio id="music1">
<source src="https://im.file.raingad.com/static/voice/calling.mp3">
</audio>
<video v-show="localStream && is_video" class="localvideo" ref="localvideo" x5-video-player-fullscreen="true" autoplay x5-playsinline playsinline webkit-playsinline @click="displayBtn = !displayBtn" poster="./image/wallpaper.png"></video>
<video v-show="remoteStream && is_video" class="remotevideo" ref="remotevideo" x5-video-player-fullscreen="true" autoplay x5-playsinline playsinline webkit-playsinline @click="changeVideo()" poster="./image/wallpaper.png"></video>
<div class="call-user-box" v-if="displayBtn">
<div class="call-user" v-if="contact">
<img class="avatar" v-if="status!=2 || !is_video" :src="contact.avatar" alt="">
<div class="text">
<b v-if="!is_video && status==2">{{contact.displayName}}</b>
<span v-if="status!=2">
<span v-if="status==3"> {{contact.displayName}} 正在请求与您{{is_video ? '视频' : '语音'}}通话</span>
<span v-else>您正对 <b>{{contact.displayName}}</b> 发起{{is_video ? '视频' : '语音'}}通话</span>
</span>
</div>
</div>
<div class="call-time" v-if="callTime && status==2">
{{setCallTime()}}
</div>
<div class="calling-button">
<div class="button" v-if="status==3" >
<img class="image" src="./image/jieting.png" @click="answer()"/>
<div class="text">接听</div>
</div>
<div class="button switch-btn" v-if="status<3" >
<img class="image-icon" :src="'./image/voice'+(voiceStatus ? '' : '-off')+'.png'" @click="switchVoice()"/>
<div class="text">{{ voiceStatus ? '关闭' : '开启'}}麦克风</div>
</div>
<div class="button" v-if="status!=0" >
<img class="image" src="./image/guaduan.png" @click="hangup(true)"/>
<div class="text">挂断</div>
</div>
<div class="button switch-btn" v-if="status<3" >
<template v-if="is_video">
<img class="image-icon" :src="'./image/video.png'" @click="exchangeVideo()"/>
<div class="text">切换摄像头</div>
</template>
<template v-else>
<img class="image-icon" :src="'./image/speaker'+(speaker ? '' : '-off')+'.png'" @click="speakBtn()"/>
<div class="text">{{ speaker ? '关闭' : '开启'}}扬声器</div>
</template>
</div>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript" src='./js/vue.js'></script>
<script>
const params=parseUrl(window.location.href);
const opt=JSON.parse(decodeURIComponent(params.stun));
const config = {
'iceServers': [{
'urls': ['stun:stun.xten.com', 'stun:stun.l.google.com:19302', 'stun:stun1.l.google.com:19302',
'stun:stun2.l.google.com:19302', 'stun:stun3.l.google.com:19302', 'stun:stun4.l.google.com:19302'
]
},{
'urls': opt.stun ? opt.stun : ['stun:stun.callwithus.com'], // 自己搭建服务器地址
"username":opt.stunUser ?? '',
"credential":opt.stunPass ?? ''
}
],
};
const Counter = {
data() {
return {
displayBtn:true,
platform:params.platform,
status: 0, //状态0默认1拨号中2通话中3来电中4忙线
pc: null, //pc实力化
localVideo: "", //本地视频的DOM
remoteVideo: "", //远程视频的DOM
remoteStream: null, // 远端视频流
localStream: null, // 本地视频流
is_video: 0, //是否为视频通话
videoStatus: true, //视频开启状态
voiceStatus: true, //语音开启状态
cutdown: 40, //拨号超时
timer: null, //计时器
offerParams:{},
plus:null,
streamType:1, //视频通话展示方式
facingMode:'user',//前置摄像头还是后置摄像头 user-前置 environment-后置
headset : true, //麦克风 打开true 关闭false,
senders: null, // 数据流
speaker:true, // 听筒 false 扬声器true
callTime:0, //通话时间
callTimeDis:'', //通话时间展示
timerIntervalId:null, //通话计时器
contact:{
id:params.target_id,
displayName:params.name,
avatar:params.avatar
}
};
},
mounted() {
this.pc = new RTCPeerConnection(config);
this.pc.ontrack = (event) => {
console.log(event,'接收视频流');
if(this.localVideo){
this.remoteStream = event.streams[0];
setTimeout(()=>{
this.streamType=2;
},50)
}
};
if (this.platform === 'app') {
document.addEventListener('plusready', () => {
console.log('设置扬声器')
this.plus = plus.audio.createPlayer();
this.plus.setRoute(plus.audio.ROUTE_SPEAKER);
});
}
this.localVideo = this.$refs.localvideo;
this.remoteVideo = this.$refs.remotevideo;
window.addEventListener('message', (e) => {
this.callMessagecallback(e)
}, false);
window.getUniAppMessage = (arg) => {
const data = {
data: jsonly(arg)
}
this.callMessagecallback(data)
}
this.is_video = params.type==1 ? true : false;
this.offerParams = this.is_video ? {
offerToRecieveAudio: 1,
offerToRecieveVideo: 1
} : {
offerToRecieveAudio: 1,
offerToRecieveVideo: 0
}
this.status=params.status
// 如果状态为1,表示拨打电话并且calling状态为1的时候才是直接拨打
if(this.status==1){
if(params.calling==1){
this.called(this.is_video)
}
}else{
this.playMusicCall('state');
}
},
watch:{
streamType(val){
// 切换镜头位置
if(val==1){
this.localVideo.srcObject = this.localStream;
this.remoteVideo.srcObject = this.remoteStream;
this.localVideo.muted=true;
this.remoteVideo.muted=false;
}else{
this.localVideo.srcObject = this.remoteStream;
this.remoteVideo.srcObject = this.localStream;
this.localVideo.muted=false;
this.remoteVideo.muted=true;
}
}
},
methods: {
// 开始通话计时
startTime() {
this.timerIntervalId=setInterval(()=>{
this.callTime++
},1000)
},
// 设置通话时间
setCallTime(){
let time=this.callTime;
const hours = Math.floor(time / 3600);
const minutes = Math.floor((time - (hours * 3600)) / 60);
const seconds = time - (hours * 3600) - (minutes * 60);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
},
// 视频电话初始化本地视频
initLocalStream(call_id, is_video) {
let video=is_video;
if(is_video){
video = {
width: window.screen.height,
height: window.screen.width
}
}
navigator.mediaDevices.getUserMedia({
video: video,
audio: {echoCancellation: true}
}).then((stream) => {
this.localStream = stream;
// 同步音频
stream.getTracks().forEach((track) => {
this.pc.addTrack(track, stream);
});
this.localVideo.srcObject = this.localStream;
// 把自己的视频静音
this.localVideo.muted = true;
if(call_id){
this.postMsg({
event:'calling',
status:3,
code:901
});
// 计时器,如果一段时间没有接听则自动挂断
this.timer = setInterval(() => {
this.cutdown--;
if (this.cutdown == 0) {
this.hangup(true);
}
}, 1000)
}else{
// 告诉对方已经接听电话
this.postMsg({ event: 'acceptRtc',code:904});
}
// 监听远程媒体流
}).catch((e) => {
this.postMsg({
event: 'mediaDevices',
})
});
},
// 拨打电话
called(is_video) {
this.is_video = is_video;
this.initLocalStream(true, is_video);
this.playMusicCall('state');
},
// 接听电话
answer() {
this.status = 2;
this.initLocalStream(false, this.is_video);
this.playMusicCall('close');
this.startTime();
},
// 挂断电话
hangup(btn) {
clearInterval(this.timer);
clearInterval(this.timerIntervalId);
if(this.status!=2){
this.playMusicCall('close');
}
if (this.status) {
this.closeLocalMedia(); //关闭本地媒体
this.remoteStream=null; //关闭远程媒体
}
// 通话取消
let code=902;
// 通话中挂断
if(this.status==2 ){
code=906
// 拒绝挂断
}else if(this.status==3 ){
code=903
//对方忙线中
}else if(this.status==4 ){
code=907
}
this.postMsg({
event:'hangup',
isbtn:btn,
callTime:this.callTime,
code:code
})
},
// 关闭本地媒体
closeLocalMedia() {
if (this.localStream && this.localStream.getTracks()) {
this.localStream.getTracks().forEach((track) => {
track.stop();
});
}
this.localStream = null;
},
// 打开或关闭声音
switchVoice() {
if (this.localStream == null) {
alert('请打开音视频');
return false;
}
const tracks = this.localStream.getTracks();
if (this.voiceStatus) {
tracks.forEach(track => {
if (track.kind === 'audio') {
track.enabled = false
}
});
this.voiceStatus = false;
} else {
tracks.forEach(track => {
if (track.kind === 'audio') {
track.enabled = true
}
});
this.voiceStatus = true;
}
},
// 临时开、关视频
switchVideo() {
if (this.localStream == null) {
alert('请打开音视频');
return false;
}
const tracks = this.localStream.getTracks();
if (this.videoStatus) {
tracks.forEach(track => {
if (track.kind === 'video') {
track.enabled = false
}
});
this.videoStatus = false;
} else {
tracks.forEach(track => {
if (track.kind === 'video') {
track.enabled = true
}
});
this.videoStatus = true;
}
},
// 切换前后摄像头
exchangeVideo() {
this.localStream.getTracks().forEach(track => track.stop());
if (this.facingMode == 'user') this.facingMode = 'environment'
else this.facingMode = 'user'
navigator.mediaDevices.getUserMedia({
video: {
width: window.screen.height,
height: window.screen.width,
facingMode: {
exact: this.facingMode
}
},
audio: {
echoCancellation: true,
}
}).then((mediastream) => {
this.senders = this.pc.getSenders()
let videoTrack = mediastream.getVideoTracks()[0];
let audioTrack = mediastream.getAudioTracks()[0];
var sender = this.senders.find((s) => {
return s.track.kind == 'video';
});
var sender2 = this.senders.find((s) => {
return s.track.kind == 'audio';
});
sender.replaceTrack(videoTrack);
sender2.replaceTrack(audioTrack);
if (this.streamType === 2) this.remoteVideo.srcObject = mediastream;
else this.localVideo.srcObject = mediastream
this.localStream = mediastream
if(this.voiceStatus==false){
this.voiceStatus=true;
this.switchVoice();
}
if(this.speaker){
this.speaker = !this.speaker
}else{
this.speaker = !this.speaker
}
})
},
// 播放响铃
playMusicCall(type) {
var audio = document.getElementById("music1");
if(type=='close' && !audio.paused){
audio.pause(); // 暂停
return;
}
if (type === "state") {
audio.loop = true;
} else {
audio.loop = false;
}
if (audio.paused) {
audio.play(); // 播放
} else {
audio.pause(); // 暂停
}
},
// 向uniapp发送消息页面通讯
postMsg(data) {
if (this.platform === 'app') {
uni.postMessage({
data: data
})
} else {
window.parent.postMessage(data)
}
},
// 接收websocket发送过来的消息,由uniapp接收后传输到当前页面
callMessagecallback(msg){
let e=msg.data;
switch (e.event) {
case "calling":
console.log('发起通话...');
this.called(this.is_video);
break;
case "hangup":
this.hangup(false);
break;
case "busy":
this.status=4;
this.hangup(false);
break;
case "acceptRtc": //已经接听创建offer并发送
this.status = 2;
clearInterval(this.timer);
this.startTime();
this.playMusicCall();
this.createOffer()
break;
case "turndown":
break;
case "answer":
//同步answer信息...
this.pc.setRemoteDescription(new RTCSessionDescription({
type: 'answer',
sdp: e.sdp
}));
break;
case "iceCandidate":
setTimeout(()=>{
// 添加ice完成通话连接
if (typeof(e.iceCandidate) === 'object') {
this.pc.addIceCandidate(new RTCIceCandidate(e.iceCandidate));
} else {
this.pc.addIceCandidate(new RTCIceCandidate(JSON.parse(e.iceCandidate)));
}
},100)
break;
case "offer":
this.pc.setRemoteDescription(new RTCSessionDescription({
type: 'offer',
sdp: e.sdp
}));
this.createAnswer();
break;
}
},
// 创建offer-sdp
createOffer() {
this.pc.createOffer(this.offerParams).then((offer) => {
this.pc.setLocalDescription(offer);
this.postMsg({
event: 'offer',
sdp: offer.sdp
}, '*');
});
// 创建offer需要监听ice流
this.onicecandidate();
},
// 创建应答sdp
createAnswer() {
this.pc.createAnswer(this.offerParams).then((answer) => {
this.pc.setLocalDescription(answer);
this.postMsg({
event: 'answer',
sdp: answer.sdp
}, '*');
this.onicecandidate();
});
},
onicecandidate(){
this.pc.onicecandidate = (event) => {
var iceCandidate = event.candidate;
if (iceCandidate) {
this.postMsg({
event: 'iceCandidate',
iceCandidate: JSON.parse(JSON.stringify(iceCandidate))
}, '*');
}
};
},
//切换视频显示位置
changeVideo(){
this.streamType==1 ? this.streamType=2 : this.streamType=1;
},
//打开关闭扬声器 h5端就是静音 ROUTE_EARPIECE 听筒 ROUTE_SPEAKER 扬声器
speakBtn() {
if (this.speaker) { //扬声器 => 听筒
this.speaker = !this.speaker
if (this.platform === 'h5') {
this.localVideo.muted = true
}
if (this.platform === 'app') {
this.plus.setRoute(plus.audio.ROUTE_EARPIECE);
}
} else { //听筒 => 扬声器
this.speaker = !this.speaker
if (this.platform === 'h5') {
this.localVideo.muted = false
}
if (this.platform === 'app') {
this.plus.setRoute(plus.audio.ROUTE_SPEAKER);
}
}
}
}
}
const app = Vue.createApp(Counter);
app.mount('#app');
</script>
</html>