Compare commits

..

5 Commits

  1. 7403
      package-lock.json
  2. 7
      package.json
  3. 37
      src/App.vue
  4. 406
      src/lib/ObsVue.vue
  5. 120
      src/lib/PdfShare.vue
  6. 45
      src/lib/RemoteFeed.vue
  7. 112
      src/lib/RemoteFeedOnly.vue
  8. 326
      src/lib/VueJanus.vue
  9. 1110
      src/lib/webrtc_adaptor.js
  10. 5
      src/main.js
  11. 2
      src/plugins/axios.js
  12. 47
      src/router/index.js
  13. 17
      src/store/index.js
  14. 720
      src/views/Animator.vue
  15. 19
      src/views/Home.vue
  16. 83
      src/views/Room.vue
  17. 81
      src/views/RoomView.vue
  18. 15
      src/views/StreamControl.vue
  19. 47
      src/views/login.vue
  20. 52
      src/views/remote.vue

7403
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -10,14 +10,21 @@
"dependencies": {
"core-js": "^3.6.4",
"jquery": "^3.4.1",
"obs-websocket-js": "^4.0.1",
"pdfjs-dist": "^2.3.200",
"vue": "^2.6.11",
"vue-native-websocket": "^2.0.14",
"vue-router": "^3.1.6",
"vue-websocket": "^0.2.3",
"vuetify": "^2.2.11",
"vuex": "^3.1.3",
"webrtc-adapter": "^7.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.2.0",
"@vue/cli-plugin-eslint": "~4.2.0",
"@vue/cli-plugin-router": "^4.3.1",
"@vue/cli-plugin-vuex": "^4.3.1",
"@vue/cli-service": "~4.2.0",
"axios": "^0.18.0",
"babel-eslint": "^10.0.3",

@ -1,36 +1,7 @@
<template>
<v-app fill-height dark>
<v-content fill-height >
<v-text-field v-if="!start" v-model="username" label="please enter name"></v-text-field> <v-btn v-if="!start" @click="startM">Start</v-btn>
<VueJanus v-if="start" server="https://vid.w3ic.org/janus" :room="room" :username="username"/>
</v-content>
</v-app>
<v-app>
<v-content>
<router-view></router-view>
</v-content></v-app>
</template>
<script>
import VueJanus from "@/lib/VueJanus";
export default {
name: 'App',
components: {
VueJanus
},
data: () => ({
test: ' ',
room:1234,
username: '',
start:false
}),
mounted () {
},
methods: {
startM () {
if (this.username) {
this.start = true
}
}
}
};
</script>

@ -0,0 +1,406 @@
<template>
<v-card class="fill-height align-center">
<v-dialog v-model="fileSystem">
<v-card>
<v-card-title>File System</v-card-title>
<v-card-text>
<v-file-input label="Upload" id="ufile"></v-file-input><v-btn @click="uploadfile">upload</v-btn>
<v-row>
<v-col cols="12" md="2" v-for="file in files" :key="file">
<v-card>
{{file}}
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
<v-dialog v-model="createSceneModel" max-width="300px">
<v-card>
<v-card-title>New Scene</v-card-title>
<v-card-text>
<v-text-field v-model="sceneName" label="Scene Name"></v-text-field>
</v-card-text>
<v-card-actions>
<v-btn text @click="createSceneModel=false">Cancel</v-btn>
<v-spacer></v-spacer>
<v-btn text color="primary" @click="createScene">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="createSceneItem" max-width="300px">
<v-card>
<v-card-title>New Source</v-card-title>
<v-card-text>
<v-text-field v-model="sourceName" label="Source Name"></v-text-field>
<v-select v-model="sourceType" item-value="value" item-text="name" :items="[{name:'Image', value:'image_source'},{name: 'VLC Source', value: 'vlc_source'},{name: 'Media Source', value: 'ffmpeg_source'},{name: 'Text', value: 'text_ft2_source_v2'}]" label="Source Type"></v-select>
<v-text-field label="Image Path" v-model="sourceSettings.file" v-if="sourceType === 'image_source'"></v-text-field>
<v-text-field label="File Path" v-model="sourceSettings.localFile" v-if="sourceType === 'ffmpeg_source'"></v-text-field>
<div v-if="sourceType === 'vlc_source'">
<div v-for="(file, index) in sourceSettings.playlist" :key="index">
{{file}}
</div>
</div>
<v-text-field v-if="sourceType === 'vlc_source'" v-model="sourceSettings.file"></v-text-field><v-btn small text @click="addPlaylist(sourceSettings.file)">+</v-btn>
</v-card-text>
<v-card-actions>
<v-btn text @click="createSceneItem=false">Cancel</v-btn>
<v-spacer></v-spacer>
<v-btn text color="primary" @click="createSource">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-card-text>
<v-row>
<v-col cols="12" md="9">
<v-card ref="mainvid" id="mainvid"
:style="`font-size:10px;height: ${vidheight}px;background: #ccc;position: relative`"
v-if="selecteScene">
<div :style="`border:1px solid #ccc;width:${source.cx*vidperm}px;z-index:${selecteScene.sources.length - index};height:${source.cy*vidperm}px;background-size: 100% 100%;top:${source.y*vidperm}px;left:${source.x*vidperm}px;position:absolute;min-width:50px;min-height:50px;background:url()`"
v-for="(source,index) in selecteScene.sources" :key="index">
<div style="top:0;left:0;position: absolute;color:#fff;text-shadow: #000 1px 1px">
{{source.name}} ({{source.id}})
</div>
<div style="top:0;right:0;position: absolute;color:#fff;text-shadow: #000 1px 1px">
{{source.type}} ({{source.source_cx}} x {{source.source_cy}} )
</div>
<img v-if="source.srcdata" width="100%" height="100%" :src="source.srcdata"/>
</div>
</v-card>
</v-col>
<v-col cols="12" md="3">
<v-card v-if="selectedItemProps">
<v-card-text>
<v-text-field label="Name" v-model="selectedItemProps.name"></v-text-field>
<v-text-field :label="`Width (Source: ${selectedItemProps.sourceWidth})`"
v-model="selectedItemProps.width" @keyup="selectedItemProps.height=selectedItemProps.width * (selectedItemProps.sourceHeight/selectedItemProps.sourceWidth)"></v-text-field>
<v-text-field :label="`Height (Source: ${selectedItemProps.sourceHeight})`"
v-model="selectedItemProps.height"></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn small @click="saveSelectedItem">Save</v-btn>
</v-card-actions>
</v-card>
<br/>
<v-card v-if="selectedItemSourceProps">
<v-card-title>Source Options
<v-spacer></v-spacer>
<v-btn icon x-smal @click="panels.source = !panels.source">
<v-icon>mdi-menu</v-icon>
</v-btn>
</v-card-title>
<v-card-text v-show="panels.source">
<v-text-field v-if="selectedItemSourceProps.sourceType === 'xcomposite_input'"
label="Capture Window"
v-model="selectedItemSourceProps.sourceSettings.capture_window"></v-text-field>
<v-text-field v-if="selectedItemSourceProps.sourceType === 'image_source'" label="File"
v-model="selectedItemSourceProps.sourceSettings.file"></v-text-field>
<div v-if="selectedItemSourceProps.sourceType === 'vlc_source'">
<div v-for="(item, index) in selectedItemSourceProps.sourceSettings.playlist"
:key="index">
<v-row>
<v-col cols="12" md="10">{{item.value}}</v-col>
<v-col cols="12" md="2">
<v-btn icon small
@click="selectedItemSourceProps.sourceSettings.playlist.splice(index,1)">
<v-icon>mdi-delete</v-icon>
</v-btn>
</v-col>
</v-row>
</div>
<v-text-field v-if="selectedItemSourceProps.sourceType === 'vlc_source'" label="File"
v-model="selectedItemSourceProps.file"></v-text-field>
<v-spacer></v-spacer>
<v-btn small
@click="selectedItemSourceProps.sourceSettings.playlist.push({ hidden: false, selected: false, value: selectedItemSourceProps.file}); selectedItemSourceProps.file=''">
Add To Playlist
</v-btn>
</div>
</v-card-text>
<v-card-actions v-show="panels.source">
<v-spacer></v-spacer>
<v-btn small>Save</v-btn>
</v-card-actions>
</v-card>
<br/>
<v-card v-if="selectedItemProps">
<v-card-title>Position
<v-spacer></v-spacer>
<v-btn icon x-smal @click="panels.position = !panels.position" l>
<v-icon>mdi-menu</v-icon>
</v-btn>
</v-card-title>
<v-card-text v-show="panels.position">
<v-text-field label="X" v-model="selectedItemProps.position.x"></v-text-field>
<v-text-field label="Y" v-model="selectedItemProps.position.y"></v-text-field>
<v-text-field label="Rotation" v-model="selectedItemProps.rotation"></v-text-field>
</v-card-text>
<v-card-actions v-show="panels.position">
<v-spacer></v-spacer>
<v-btn small>Save</v-btn>
</v-card-actions>
</v-card>
<br/>
<v-card v-if="selectedItemProps">
<v-card-title>Crop
<v-spacer></v-spacer>
<v-btn icon x-smal @click="panels.crop = !panels.crop">
<v-icon>mdi-menu</v-icon>
</v-btn>
</v-card-title>
<v-card-text v-show="panels.crop">
<v-text-field label="Top" v-model="selectedItemProps.crop.top"></v-text-field>
<v-text-field label="Left" v-model="selectedItemProps.crop.left"></v-text-field>
<v-text-field label="Bottom" v-model="selectedItemProps.crop.bottom"></v-text-field>
<v-text-field label="Right" v-model="selectedItemProps.crop.right"></v-text-field>
</v-card-text>
<v-card-actions v-show="panels.crop">
<v-spacer></v-spacer>
<v-btn small>Save</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="3">
<v-list>
<v-list-item-group v-model="selecteSceneName" color="primary">
<v-list-item v-for="(scene,index) in scenes" :key="index" @click="setSelect(scene)">
<v-list-item-content>
<v-list-item-title v-text="scene.name"></v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item>
<v-list-item-content>
<v-list-item-title @click="createSceneModel=true">New Scene</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-col>
<v-col cols="12" md="3">
<v-list v-if="selecteScene">
<v-list-item v-for="(source,index) in selecteScene.sources" :key="index"
@click="setItemSelect(source)">
<v-list-item-content>
<v-list-item-title v-text="source.name"></v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item>
<v-list-item-content>
<v-list-item-title @click="createSceneItem=true">New Item</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>
<script>
const OBSWebSocket = require('obs-websocket-js');
export default {
name: "ObsVue",
props: {
server: String
},
data: () => ({
obs: null,
selectedItemProps: null,
selectedItemSourceProps: null,
createSceneModel: false,
createSceneItem: false,
sourceSettings: {
playlist: [],
file: '',
localFile: ''
},
files: [
'test.jpg',
'test.png'
],
sourceType: '',
sourceName: '',
scenes: [],
sources: [],
panels: {
source: false,
position: false,
crop: false
},
previewInterval: 0,
selecteScene: null,
sceneName: '',
vidheight: 0,
vidperm: 0,
selecteSceneName: null,
fileSystem: true
}),
mounted() {
this.obs = new OBSWebSocket()
this.getfiles()
this.obs.connect({address: 'ws://localhost:4444', password: 'neverdie'}).then(this.connected)
this.obs.on('error', err => {
console.error('socket error:', err);
});
},
methods: {
uploadfile() {
var file = document.getElementById('ufile')
console.log(file.files[0])
var filereader = new FileReader()
filereader.onloadend = () => {
this.$axios.post('http://localhost:5000/api/upload', {"file": filereader.result.split(';base64,')[1], 'file_name': file.files[0].name}).then(this.getfiles)
}
filereader.readAsDataURL(file.files[0]);
},
getfiles () {
this.$axios.get('http://localhost:5000/api/files').then(result => {
this.files = result.data
})
},
addPlaylist(item) {
this.sourceSettings.playlist.push({hidden: false, selected: false, value:item})
console.log(this.sourceSettings)
},
createScene() {
this.obs.send('CreateNewScene', {'sceneName': this.sceneName}).then(response => {
console.log(response)
this.sceneName = ''
this.createSceneModel = false
this.sceneList()
})
},
createSource() {
var sourceSetting;
if (this.sourceType === 'vlc_source') sourceSetting = {playlist: this.sourceSettings.playlist}
if (this.sourceType === 'ffmpeg_source') sourceSetting = {localfile: this.sourceSettings.localfile}
if (this.sourceType === 'image_source') sourceSetting = {file: this.sourceSettings.file}
console.log({sourceName: this.sourceName,sourceSettings: sourceSetting, sourceType: this.sourceType })
this.obs.send('CreateNewSource', {sourceName: this.sourceName,sourceSettings: sourceSetting, sourceType: this.sourceType }).then(response => {
console.log(response)
this.sceneName = ''
this.createSceneModel = false
this.obs.send('AddToScene', {sourceName:this.sourceName })
this.sceneList()
})
},
onAuth() {
console.log("auth")
},
newScene() {
},
setItemSelect(item) {
this.obs.send("GetSceneItemProperties", {
item: item.name,
"scene-name": this.selecteScene.name
}).then(data => {
console.log("GetSceneItemProperties")
console.log(data)
this.selectedItemProps = data
})
this.obs.send("GetSourceSettings", {sourceName: item.name}).then(data => {
console.log("GetSourceSettings")
console.log(data)
this.selectedItemSourceProps = data
})
},
setSelect(item) {
console.log(item)
this.selecteScene = item
this.selecteSceneName = item.name
this.obs.send('GetSourcesList').then(data => {
console.log('GetSourcesList')
console.log(data)
})
if (this.previewInterval > 0) clearInterval(this.previewInterval)
this.previewInterval = setInterval(() => {
this.selecteScene.sources.forEach(sitem => {
this.obs.send('TakeSourceScreenshot', {
sourceName: sitem.name,
embedPictureFormat: 'png'
}).then(data => {
sitem.srcdata = data.img
this.selecteScene.__ob__.dep.notify()
this.vidheight = document.getElementById('mainvid').offsetWidth / (16 / 9)
this.vidperm = this.vidheight / 1080.0
}).catch(err => {
console.log(err)
})
})
}, 500)
},
saveSelectedItem() {
console.log( {
'item': this.selectedItemProps.name,
position: {
x: parseInt(this.selectedItemProps.position.x),
y: parseInt(this.selectedItemProps.position.y),
},
crop: this.selectedItemProps.crop,
rotation: this.selectedItemProps.rotation,
height: this.selectedItemProps.height,
scale: {x:this.selectedItemProps.width/this.selectedItemProps.sourceWidth,y:this.selectedItemProps.height/this.selectedItemProps.sourceHeight}
})
this.obs.send('SetSceneItemProperties', {
item: this.selectedItemProps.name,
position: {
x: parseInt(this.selectedItemProps.position.x),
y: parseInt(this.selectedItemProps.position.y),
},
crop: {
top:parseInt(this.selectedItemProps.crop.top),
bottom:parseInt(this.selectedItemProps.crop.bottom),
left:parseInt(this.selectedItemProps.crop.left),
right:parseInt(this.selectedItemProps.crop.right),
},
rotation: this.selectedItemProps.rotation,
height: this.selectedItemProps.height,
scale: {x:this.selectedItemProps.width/this.selectedItemProps.sourceWidth,y:this.selectedItemProps.height/this.selectedItemProps.sourceHeight}
}).then(data => {
console.log(data)
this.sceneList()
})
},
sceneList() {
this.obs.send('GetSceneList').then(data => {
console.log("scene", data)
this.scenes = data.scenes
data.scenes.forEach(item => {
if (item.name === data["current-scene"]) {
this.setSelect(item)
}
})
})
},
connected() {
this.obs.send('GetVersion').then(data => {
this.obs.send('GetVersion').then(data2 => {
console.log(data)
console.log(data2)
})
})
this.sceneList()
}
}
}
</script>
<style scoped>
</style>

@ -25,6 +25,7 @@
<script>
import pdfjsLib from 'pdfjs-dist/webpack';
import WebRTCAdaptor from "@/lib/webrtc_adaptor";
const toBase64 = file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
@ -39,20 +40,114 @@ export default {
onstop: Function,
username: String,
myrivid: Number,
streamer:Boolean,
room: Number,
opaqueId:String
},
data: () => ({
pdf: null,
pageNumber: 1,
loading: true,
pluginHandle: null,
intervalId: null
pdf:null,
pageNumber:1,
loading:true,
pluginHandle: null
}),
mounted () {
pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdf.worker.js"
},
methods: {
streamerScreenShare(stream) {
let websocketURL = "wss://live.ozgurkon.org/LiveApp/websocket"
var mediaConstraints = {
video: { width: 1280, height: 720 },
audio: false
};
var pc_config = null;
var sdpConstraints = {
OfferToReceiveAudio: false,
OfferToReceiveVideo: false
};
if (this.streamer) {
var tt = this
this.antwrtcScreen = new WebRTCAdaptor({
websocket_url: websocketURL,
mediaConstraints: mediaConstraints,
peerconnection_config: pc_config,
sdp_constraints: sdpConstraints,
janusScreen:stream,
localVideoId: "myvideo2",
debug: true,
callback: function (info, obj) {
if (info == "initialized") {
console.log("initialized");
tt.antwrtcScreen.publish(tt.username+"-pdf", "null");
// start_publish_button.disabled = false;
// stop_publish_button.disabled = true;
} else if (info == "publish_started") {
//stream is being published
console.log("publish started");
// start_publish_button.disabled = true;
// stop_publish_button.disabled = false;
// startAnimation();
} else if (info == "publish_finished") {
//stream is being finished
console.log("publish finished");
this.startPublishing(tt.username + "-pdf");
// start_publish_button.disabled = false;
// stop_publish_button.disabled = true;
} else if (info == "screen_share_extension_available") {
// screen_share_checkbox.disabled = false;
// console.log("screen share extension available");
// install_extension_link.style.display = "none";
} else if (info == "screen_share_stopped") {
console.log("screen share stopped");
} else if (info == "closed") {
//console.log("Connection closed");
if (typeof obj != "undefined") {
console.log("Connecton closed: " + JSON.stringify(obj));
}
} else if (info == "pong") {
//ping/pong message are sent to and received from server to make the connection alive all the time
//It's especially useful when load balancer or firewalls close the websocket connection due to inactivity
} else if (info == "refreshConnection") {
this.startPublishing(tt.username + "-pdf");
} else if (info == "updated_stats") {
//obj is the PeerStats which has fields
//averageOutgoingBitrate - kbits/sec
//currentOutgoingBitrate - kbits/sec
console.log("Average outgoing bitrate " + obj.averageOutgoingBitrate + " kbits/sec"
+ " Current outgoing bitrate: " + obj.currentOutgoingBitrate + " kbits/sec");
}
},
callbackError: function (error, message) {
//some of the possible errors, NotFoundError, SecurityError,PermissionDeniedError
console.log("error callback: " + JSON.stringify(error));
var errorMessage = JSON.stringify(error);
if (typeof message != "undefined") {
errorMessage = message;
}
if (error.indexOf("NotFoundError") != -1) {
errorMessage = "Camera or Mic are not found or not allowed in your device";
} else if (error.indexOf("NotReadableError") != -1 || error.indexOf("TrackStartError") != -1) {
errorMessage = "Camera or Mic is being used by some other process that does not let read the devices";
} else if (error.indexOf("OverconstrainedError") != -1 || error.indexOf("ConstraintNotSatisfiedError") != -1) {
errorMessage = "There is no device found that fits your video and audio constraints. You may change video and audio constraints"
} else if (error.indexOf("NotAllowedError") != -1 || error.indexOf("PermissionDeniedError") != -1) {
errorMessage = "You are not allowed to access camera and mic.";
} else if (error.indexOf("TypeError") != -1) {
errorMessage = "Video/Audio is required";
}
alert(errorMessage);
}
});
}
},
renderPage(pageNumber) {
this.pdf.getPage(pageNumber).then(page => {
console.log('Page loaded');
@ -74,9 +169,8 @@ export default {
var renderTask = page.render(renderContext);
renderTask.promise.then( () => {
this.loading = false
if (!this.pluginHandle) {
this.loading = false
// document.getElementById('pdfvid').srcObject = document.getElementById('mypdfview').captureStream(60)
this.janusInit.attach(
@ -110,7 +204,6 @@ export default {
this.pluginHandle.detach();
// reset
clearInterval(this.intervalId);
this.pluginHandle = null;
this.pdf = null;
this.pageNumber = 1;
@ -129,7 +222,7 @@ export default {
console.log("Janus " + (on ? "started" : "stopped") + " receiving our " + medium);
},
webrtcState (on) {
this.pluginHandle.send({"message": { "request": "configure", "bitrate": 1024*1000 }});
this.pluginHandle.send({"message": { "request": "configure", "bitrate": 512*1000 }});
console.log(on, "webrt")
},
janusMessage (msg, jsep) {
@ -194,8 +287,13 @@ export default {
onlocalstream (stream) {
// Janus.attachMediaStream($('#myvideo').get(0), stream);
console.log('pdf --------- onlocalstream')
console.log('pdf --------- onlocalstream')
console.log('pdf --------- onlocalstream')
console.log('pdf --------- onlocalstream')
console.log('pdf --------- onlocalstream')
this.streamerScreenShare(stream)
console.log(stream)
},
async loadFile() {
@ -211,7 +309,7 @@ export default {
// var pageNumber = 1;
this.pdf = pdf
this.loading = true
this.intervalId = setInterval(this.pdfTimer, 1000/15)
setInterval(this.pdfTimer, 1000/15)
this.renderPage(1)

@ -1,11 +1,15 @@
<template>
<v-card max-height="150" height="150" dark
<div>
<v-card dark v-if="!singleView"
:color="remoteStream.talking && !singleView?'green':''"
class="vid_main" @mouseleave="showaction = false" @mouseover="showaction = true">
<video ref="feed_video" class="vid_container" autoplay playsinline/>
<div class="infor">{{ bitrate }}</div>
<div class="infoSlow">Slow Connection</div>
<div class="infoName">{{ remoteStream.display }}</div>
<div class="actions" v-show="showaction">
<video ref="feed_video" class="vid_container" width="99%" height="99%" autoplay playsinline/>
<div v-if="!singleView" class="infor">{{ bitrate }}</div>
<div class="kick" v-if="creator && !singleView"><v-btn icon @click="$emit('kick',feedid)"><v-icon>mdi-delete</v-icon></v-btn></div>
<div class="rtp" v-if="creator && !singleView"><v-btn icon @click="$emit('rtp',feedid)"><v-icon>mdi-pencil</v-icon></v-btn></div>
<div v-if="!singleView" class="infoSlow">Slow Connection</div>
<div v-if="!singleView" class="infoName">{{ remoteStream.display }}</div>
<div v-if="!singleView" class="actions" v-show="showaction">
<v-slider
v-model="volume"
:min="0"
@ -14,6 +18,9 @@
></v-slider>
</div>
</v-card>
<video ref="feed_video" width="1280" height="720" v-else class="vid_container" autoplay playsinline/>
</div>
</template>
<script>
@ -22,7 +29,9 @@
janusInit: Object,
opaqueId: String,
room: Number,
singleView: Boolean,
mypvtid: Number,
creator: Boolean,
feedid: Number,
remoteStream: Object
},
@ -36,6 +45,9 @@
volume: 100
}),
watch: {
feedid () {
document.location.reload()
},
volume (old, newVal) {
this.$refs.feed_video.volume = parseFloat(newVal) * 0.01
@ -71,6 +83,7 @@
this.remoteFeed.simulcastStarted = false;
console.log('Remote feed success')
var subscribe = { "request": "join", "room": this.room, "ptype": "subscriber", "feed": this.feedid, "private_id": this.mypvtid };
console.log(subscribe)
this.remoteFeed.send({"message": subscribe});
@ -80,13 +93,16 @@
},
onStream (stream) {
console.log('stream is strarted')
this.$emit('stream',stream)
if (this.$refs.feed_video) {
try {
this.$refs.feed_video.srcObject = stream
} catch (e) {
this.$refs.feed_video.src = URL.createObjectURL(stream)
}
this.$refs
} else {
console.log(this.$refs.feed_video)
}
@ -96,6 +112,7 @@
this.bitrate = this.remoteFeed.getBitrate()
},
feedResponse (jsep) {
console.log(jsep)
var body = { "request": "start", "room": this.room }
this.remoteFeed.send({"message": body, "jsep": jsep})
},
@ -108,7 +125,6 @@
if (msg["janus"]) {
console.log("######################")
console.log("nn",msg)
}
console.log("-------------------------")
var event = msg["videoroom"];
@ -117,7 +133,6 @@
} else if(event !== undefined && event != null) {
this.remoteFeed.rfid = msg["id"]
this.remoteFeed.rfdisplay = msg["display"]
} else if(event === "event") {
var substream = msg["substream"]
var temporal = msg["temporal"]
@ -174,6 +189,20 @@
font-size: 10px;
text-shadow: 1px 1px #000;
}
.kick {
position: absolute;
right: 0;
top:30px;
font-size: 10px;
text-shadow: 1px 1px #000;
}
.rtp {
position: absolute;
left: 0;
top:30px;
font-size: 10px;
text-shadow: 1px 1px #000;
}
.infoName {
position: absolute;
left: 5px;

@ -0,0 +1,112 @@
<template>
<RemoteFeed v-if="remoteStream" :single-view="true" :opaqueId="opaqueId" :mypvtid="mypvtid" :feedid="remoteStream.id" :remote-stream="remoteStream" :janusInit="janusInit" :room="1234" ></RemoteFeed>
</template>
<script>
import RemoteFeed from "@/lib/RemoteFeed";
export default {
name: "RemoteFeedOnly",
props: {
server: String,
room: Number,
user: String
},
components: {RemoteFeed},
data: () => ({
remoteStream: null,
opaqueId: null,
mypvtid: null,
janusInit: null,
run: false
}),
mounted() {
this.janusInit = new this.$janus({
success: this.janusSuccess,
server: this.server,
error: function (error) {
this.$janus.error(error);
},
//webrtcState: this.webrtcState,
destroyed: function () {
window.location.reload();
}
})
},
methods: {
janusMessage(msg, jsep) {
console.log(msg);
var event = msg["videoroom"];
if (event !== undefined) {
if (event === "joined") {
this.myid = msg["id"];
this.mypvtid = msg["private_id"];
this.$janus.log("Successfully joined room " + msg["room"] + " with ID " + this.myid);
// this.restartDevices()
if (msg["publishers"] !== undefined && msg["publishers"] !== null) {
msg['publishers'].forEach(publisher => {
console.log(publisher.id)
console.log(publisher)
if (publisher.display === this.user) {
this.remoteStream = publisher
this.run = true
}
});
}
} else if (event === "event") {
if (msg["publishers"] !== undefined && msg["publishers"] !== null) {
msg['publishers'].forEach(publisher => {
console.log(publisher.id)
console.log(publisher)
if (publisher.display === this.user) {
this.remoteStream = publisher
this.run = true
}
});
} else if (msg["leaving"] !== undefined && msg["leaving"] !== null) {
console.log("leaving")
}
}
}
if (jsep !== undefined && jsep !== null) {
console.log("Handling SDP as well...");
console.log(jsep);
this.pluginHandle.handleRemoteJsep({jsep: jsep});
var audio = msg["audio_codec"];
if (this.mystream && this.mystream.getAudioTracks() && this.mystream.getAudioTracks().length > 0 && !audio) {
console.log("Our audio stream has been rejected, viewers won't hear us");
}
}
},
janusPluginSuccess (pluginHandle) {
this.pluginHandle = pluginHandle
this.$janus.log("Plugin attached! (" + this.pluginHandle.getPlugin() + ", id=" + this.pluginHandle.getId() + ")");
this.$janus.log(" -- This is a publisher/manager");
var register = { "request": "join", "room": this.room, "ptype": "publisher" };
pluginHandle.send({"message": register});
},
janusSuccess() {
this.opaqueId = "videoroomtest-" + this.$janus.randomString(12);
this.janusInit.attach(
{
plugin: "janus.plugin.videoroom",
opaqueId: this.opaqueId,
success: this.janusPluginSuccess,
error: this.janusPluginError,
onmessage: this.janusMessage,
}
)
}
}
}
</script>
<style scoped>
</style>

@ -1,7 +1,6 @@
<template>
<v-container fill-height>
<PdfShare v-if="sharePdf" :username="username" :opaque-id="opaqueId" :room="room" :onstop="stopSharePdf" :janus-init="janusInit" ></PdfShare>
<PdfShare v-if="sharePdf" :streamer="streamer" :username="username" :opaque-id="opaqueId" :room="1234" :janus-init="janusInit" ></PdfShare>
<v-dialog
v-model="mozillaAlert"
max-width="600"
@ -86,18 +85,37 @@
</v-card-actions>
</v-card>
</v-dialog>
<v-row>
<v-col cols="12" md="2">
<video ref="ownstream" class="rounded centered" id="myvideo" width="100%" height="100%" autoplay
playsinline muted="muted"/>
<div v-show="remoteViewNew">
<div style="width: 100%;height: 100%;background: #ccc;position: absolute;top:0;left:0"></div>
<v-row>
<v-col cols="12" md="10"></v-col>
<v-col cols="12" md="2">
<v-row>
<v-col cols="12" md="12" v-for="remoteStream in remoteStreams" :key="remoteStream.id">
<RemoteFeed v-if="remoteViewNew" @kick="kickUser" @rtp="$emit('rtp',remoteStream.id)" :creator="creator" :talking="remoteStream.talking" :opaqueId="opaqueId" :mypvtid="mypvtid" :feedid="remoteStream.id" :remote-stream="remoteStream" :janusInit="janusInit" :room="1234" ></RemoteFeed>
</v-col>
</v-row>
</v-col>
</v-row>
</div>
<v-row v-show="!remoteViewNew">
<v-col cols="12" :md="mdsize">
<div width="100%" height="100%" style="position: relative">
<video ref="ownstream" v-if="streamer" class="rounded centered" id="myvideo" width="100%" height="100%" autoplay
playsinline muted="muted"/>
<video ref="ownstream" v-if="!streamer" class="rounded centered" id="myvideo" width="100%" height="100%" autoplay
playsinline muted="muted"/>
<v-btn icon v-if="ownmuted" @click="muteMe"><v-icon>mdi-volume-mute</v-icon></v-btn>
<v-btn icon v-if="!ownmuted" @click="muteMe"><v-icon>mdi-volume-high</v-icon></v-btn>
</div>
</v-col>
<v-col cols="12" md="2" style="display: none">
<v-col cols="12" :md="mdsize">
<video ref="ownstreamscreen" class="rounded centered" id="myvideo2" width="100%" height="100%" autoplay
playsinline muted="muted"/>
</v-col>
<v-col cols="12" md="2" v-for="remoteStream in remoteStreams" :key="remoteStream.id">
<RemoteFeed :opaqueId="opaqueId" :mypvtid="mypvtid" :feedid="remoteStream.id" :remote-stream="remoteStream" :janusInit="janusInit" :room="room" ></RemoteFeed>
<v-col cols="12" :md="mdsize" v-for="remoteStream in remoteStreams" :key="remoteStream.id">
<RemoteFeed v-if="!remoteViewNew" @kick="kickUser" @rtp="$emit('rtp',remoteStream.id)" :creator="creator" :talking="remoteStream.talking" :opaqueId="opaqueId" :mypvtid="mypvtid" :feedid="remoteStream.id" :remote-stream="remoteStream" :janusInit="janusInit" :room="1234" ></RemoteFeed>
</v-col>
</v-row>
<v-speed-dial
@ -151,6 +169,21 @@
<span>Toggle Theme</span>
</v-tooltip>
<v-tooltip right>
<template v-slot:activator="{ on }">
<v-btn
v-on="on"
fab
dark
small
@click="remoteViewNew = !remoteViewNew"
>
<v-icon>mdi-compare</v-icon>
</v-btn>
</template>
<span>Toggle View</span>
</v-tooltip>
<v-tooltip right>
<template v-slot:activator="{ on }">
@ -191,26 +224,34 @@
<script>
import RemoteFeed from "@/lib/RemoteFeed";
import PdfShare from "@/lib/PdfShare";
import WebRTCAdaptor from "@/lib/webrtc_adaptor";
export default {
components: {PdfShare, RemoteFeed},
props: {
server: String,
room: Number,
username: String
stream: Boolean,
username: String,
creator: Boolean,
streamer: Boolean
},
data() {
return {
janusInit: null,
antwrtc: null,
opaqueId: null,
pluginHandle: null,
mystream: null,
myid: '',
mdsize:6,
ownmuted: false,
mypvtid: '',
selectedVideo: null,
selectedAudio: null,
started: false,
mozillaAlert: false,
sharePdf: false,
remoteViewNew: false,
janusScreenShareHandle: null,
chromeAlert: false,
videoDevices: [],
@ -221,7 +262,8 @@
fab: true,
shareScreenId: null,
remoteStreams: [],
screenShareStarted: false
screenShareStarted: false,
antwrtcScreen: false
}
},
name: "VueJanus",
@ -240,31 +282,150 @@
},
methods: {
kickUser (user) {
this.$emit('kick',user)
},
muteMe () {
this.ownmuted = !this.ownmuted
if(!this.ownmuted) {
this.pluginHandle.unmuteAudio()
} else {
this.pluginHandle.muteAudio()
}
},
stopSharePdf(){
this.sharePdf=false;
},
webrtcState (on) {
this.pluginHandle.send({"message": { "request": "configure", "bitrate": 512*1000 }});
this.pluginHandle.send({"message": { "request": "configure", "bitrate": 128*1000 }});
console.log(on, "webrt")
},
webrtcState2 (on) {
this.pluginHandle.send({"message": { "request": "configure", "bitrate": 128*1000 }});
console.log(on, "webrt")
//this.antwrtc.publish(this.username, "null");
},
janusPluginSuccessScreen (pluginHandle) {
this.janusScreenShareHandle = pluginHandle
this.$janus.log("Plugin attached! (" + this.pluginHandle.getPlugin() + ", id=" + this.pluginHandle.getId() + ")");
this.$janus.log(" -- This is a publisher/manager");
var register = { "request": "join", "room": this.room, "ptype": "publisher", "display": this.username + " - Share Screen" };
var register = { "request": "join", "room": 1234, "ptype": "publisher", "display": this.username + " - Share Screen" };
pluginHandle.send({"message": register});
},
castStart(jsep) {
var publish = { "request": "configure", "audio": false, "video": true };
this.janusScreenShareHandle.send({"message": publish, "jsep": jsep});
},
streamerScreenShare(stream) {
let websocketURL = "wss://live.ozgurkon.org/LiveApp/websocket"
var mediaConstraints = {
video: { width: 1280, height: 720 },
audio: false
};
var pc_config = null;
var sdpConstraints = {
OfferToReceiveAudio: false,
OfferToReceiveVideo: false
};
if (this.streamer) {
var tt = this
this.antwrtcScreen = new WebRTCAdaptor({
websocket_url: websocketURL,
mediaConstraints: mediaConstraints,
peerconnection_config: pc_config,
sdp_constraints: sdpConstraints,
janusScreen:stream,
localVideoId: "myvideo2",
debug: true,
callback: function (info, obj) {
if (info == "initialized") {
console.log("initialized");
tt.antwrtcScreen.publish(tt.username+"-desktop", "null");
// start_publish_button.disabled = false;
// stop_publish_button.disabled = true;
} else if (info === "publish_started") {
//stream is being published
console.log("publish started");
// start_publish_button.disabled = true;
// stop_publish_button.disabled = false;
// startAnimation();
} else if (info == "publish_finished") {
//stream is being finished
console.log("publish finished");
// start_publish_button.disabled = false;
// stop_publish_button.disabled = true;
} else if (info == "screen_share_extension_available") {
// screen_share_checkbox.disabled = false;
// console.log("screen share extension available");
// install_extension_link.style.display = "none";
} else if (info == "screen_share_stopped") {
console.log("screen share stopped");
} else if (info == "closed") {
//console.log("Connection closed");
if (typeof obj != "undefined") {
console.log("Connecton closed: " + JSON.stringify(obj));
}
} else if (info == "pong") {
//ping/pong message are sent to and received from server to make the connection alive all the time
//It's especially useful when load balancer or firewalls close the websocket connection due to inactivity
} else if (info == "refreshConnection") {
this.startPublishing();
} else if (info == "updated_stats") {
//obj is the PeerStats which has fields
//averageOutgoingBitrate - kbits/sec
//currentOutgoingBitrate - kbits/sec
console.log("Average outgoing bitrate " + obj.averageOutgoingBitrate + " kbits/sec"
+ " Current outgoing bitrate: " + obj.currentOutgoingBitrate + " kbits/sec");
}
},
callbackError: function (error, message) {
//some of the possible errors, NotFoundError, SecurityError,PermissionDeniedError
console.log("error callback: " + JSON.stringify(error));
var errorMessage = JSON.stringify(error);
if (typeof message != "undefined") {
errorMessage = message;
}
if (error.indexOf("NotFoundError") != -1) {
errorMessage = "Camera or Mic are not found or not allowed in your device";
} else if (error.indexOf("NotReadableError") != -1 || error.indexOf("TrackStartError") != -1) {
errorMessage = "Camera or Mic is being used by some other process that does not let read the devices";
} else if (error.indexOf("OverconstrainedError") != -1 || error.indexOf("ConstraintNotSatisfiedError") != -1) {
errorMessage = "There is no device found that fits your video and audio constraints. You may change video and audio constraints"
} else if (error.indexOf("NotAllowedError") != -1 || error.indexOf("PermissionDeniedError") != -1) {
errorMessage = "You are not allowed to access camera and mic.";
} else if (error.indexOf("TypeError") != -1) {
errorMessage = "Video/Audio is required";
}
alert(errorMessage);
}
});
}
},
janusMessageScreen (msg, jesp) {
var event = msg["videoroom"]
console.log(jesp)
if(event === "joined") {
this.janusScreenShareHandle.createOffer(
{
media: { video: this.capture, audioSend: false, videoRecv: false}, // Screen sharing Publishers are sendonly
simulcast:true,
simulcast2:true,
success: this.castStart,
error: function(error) {
console.log("WebRTC error:", error);
@ -281,7 +442,7 @@
onlocalstreamScreen (stream) {
if(this.screenShareStarted)
this.screenShareStarted=true;
this.streamerScreenShare(stream)
this.$refs.ownstreamscreen.srcObject = stream
},
screenShareSwitch () {
@ -301,7 +462,7 @@
consentDialog: this.consentDialog,
error: this.janusPluginError,
mediaState: this.mediaState,
webrtcState: this.webrtcState,
webrtcState: this.webrtcState2,
onmessage: this.janusMessageScreen,
onlocalstream: this.onlocalstreamScreen
}
@ -349,7 +510,6 @@
this.$janus.listDevices(this.initDevices);
},
initDevices (devices) {
console.log(devices)
@ -371,13 +531,27 @@
},
offerSend (jsep) {
console.log('vid offer')
console.log(jsep)
var publish = {"request": "configure", "audio": true, "video": true};
this.pluginHandle.send({"message": publish, "jsep": jsep});
},
controlPublishers(msg) {
if (msg["publishers"].length > 12) {
this.mdsize = 1
} else {
var size = parseInt(18 / (msg["publishers"].length+1))
if(size<6) {
this.mdsize = size
} else {
this.mdsize = size /2
}
if (this.mdsize > 6) this.mdsize = 6
this.mdsize = parseInt(this.mdsize)
}
msg["publishers"].forEach(item => {
var has = false
item.talking = false
this.remoteStreams.forEach(rm => {
if (item.id === rm.id) has = true
})
@ -390,7 +564,19 @@
janusMessage (msg, jsep) {
console.log(msg);
var event = msg["videoroom"];
if (event !== undefined) {
if (event === "talking") {
this.remoteStreams.forEach(item => {
if (item.id === msg['id']) item.talking = true
})
}
if (event === "stopped-talking") {
this.remoteStreams.forEach(item => {
if (item.id === msg['id']) item.talking = false
})
}
if (event === "joined") {
this.myid = msg["id"];
this.mypvtid = msg["private_id"];
@ -434,18 +620,24 @@
var media = {
audioRecv: false, videoRecv: false, audioSend: true, videoSend: !this.noVid,
simulcast:true,
simulcast2:true,
audio: {
deviceId: {
exact: this.selectedAudio
}
},
replaceAudio: true, // This is only needed in case of a renegotiation
// replaceAudio: true, // This is only needed in case of a renegotiation
video: {
deviceId: {
exact: this.selectedVideo
}
exact: this.selectedVideo,
width:1280,
height: 720
},
width:1280,
height: 720
},
replaceVideo: true,
// replaceVideo: true,
};
@ -466,18 +658,104 @@
this.$refs.ownstream.setSinkId(this.selectedVideo)
this.$refs.ownstream.setSinkId(this.selectedAudio)
},
selectDevice () {
selectDevice: function () {
if (!this.started) {
console.log(this.selectedVideo)
console.log(this.selectedAudio)
this.started = true
var register = {
"request": "join",
"room": this.room,
"room": 1234,
"ptype": "publisher",
"display": this.username
};
this.pluginHandle.send({"message": register});
let websocketURL = "wss://live.ozgurkon.org/LiveApp/websocket"
var mediaConstraints = {
video: { width: 1280, height: 720 },
audio: true
};
var pc_config = null;
var sdpConstraints = {
OfferToReceiveAudio: false,
OfferToReceiveVideo: false
};
if (this.streamer) {
this.antwrtc = new WebRTCAdaptor({
websocket_url: websocketURL,
mediaConstraints: mediaConstraints,
peerconnection_config: pc_config,
sdp_constraints: sdpConstraints,
localVideoId: "myvideo",
debug: true,
callback: function (info, obj) {
if (info == "initialized") {
console.log("initialized");
// start_publish_button.disabled = false;
// stop_publish_button.disabled = true;
} else if (info == "publish_started") {
//stream is being published
console.log("publish started");
// start_publish_button.disabled = true;
// stop_publish_button.disabled = false;
// startAnimation();
} else if (info == "publish_finished") {
//stream is being finished
console.log("publish finished");
// start_publish_button.disabled = false;
// stop_publish_button.disabled = true;
} else if (info == "screen_share_extension_available") {
// screen_share_checkbox.disabled = false;
// console.log("screen share extension available");
// install_extension_link.style.display = "none";
} else if (info == "screen_share_stopped") {
console.log("screen share stopped");
} else if (info == "closed") {
//console.log("Connection closed");
if (typeof obj != "undefined") {
console.log("Connecton closed: " + JSON.stringify(obj));
}
} else if (info == "pong") {
//ping/pong message are sent to and received from server to make the connection alive all the time
//It's especially useful when load balancer or firewalls close the websocket connection due to inactivity
} else if (info == "refreshConnection") {
this.startPublishing();
} else if (info == "updated_stats") {
//obj is the PeerStats which has fields
//averageOutgoingBitrate - kbits/sec
//currentOutgoingBitrate - kbits/sec
console.log("Average outgoing bitrate " + obj.averageOutgoingBitrate + " kbits/sec"
+ " Current outgoing bitrate: " + obj.currentOutgoingBitrate + " kbits/sec");
}
},
callbackError: function (error, message) {
//some of the possible errors, NotFoundError, SecurityError,PermissionDeniedError
console.log("error callback: " + JSON.stringify(error));
var errorMessage = JSON.stringify(error);
if (typeof message != "undefined") {
errorMessage = message;
}
if (error.indexOf("NotFoundError") != -1) {
errorMessage = "Camera or Mic are not found or not allowed in your device";
} else if (error.indexOf("NotReadableError") != -1 || error.indexOf("TrackStartError") != -1) {
errorMessage = "Camera or Mic is being used by some other process that does not let read the devices";
} else if (error.indexOf("OverconstrainedError") != -1 || error.indexOf("ConstraintNotSatisfiedError") != -1) {
errorMessage = "There is no device found that fits your video and audio constraints. You may change video and audio constraints"
} else if (error.indexOf("NotAllowedError") != -1 || error.indexOf("PermissionDeniedError") != -1) {
errorMessage = "You are not allowed to access camera and mic.";
} else if (error.indexOf("TypeError") != -1) {
errorMessage = "Video/Audio is required";
}
alert(errorMessage);
}
});
}
} else {
this.restartDevices()
}

File diff suppressed because it is too large Load Diff

@ -3,12 +3,15 @@ import './plugins/axios'
import JanusPlugin from "@/plugins/janus"
import App from './App.vue'
import vuetify from './plugins/vuetify';
import router from './router'
import store from './store'
Vue.config.productionTip = false
console.log("janus is installed", Vue.$janus)
new Vue({
vuetify,
JanusPlugin,
router,
store,
render: h => h(App)
}).$mount('#app')

@ -9,7 +9,7 @@ import axios from "axios";
// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
let config = {
// baseURL: process.env.baseURL || process.env.apiUrl || ""
baseURL: process.env.baseURL || process.env.apiUrl || "http://localhost:5000/api/"
// timeout: 60 * 1000, // Timeout
// withCredentials: true, // Check cross-site Access-Control
};

@ -0,0 +1,47 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "login" */ '../views/login.vue')
}, {
path: '/',
name: 'Home',
component: () => import(/* webpackChunkName: "Home" */ '../views/Home.vue')
},
{
path: '/rooms',
name: 'Room',
component: () => import(/* webpackChunkName: "Room" */ '../views/Room.vue')
},{
path: '/animator',
name: 'Animator',
component: () => import(/* webpackChunkName: "Animator" */ '../views/Animator.vue')
},
{
path: '/room/:id',
name: 'RoomView',
component: () => import(/* webpackChunkName: "RoomView" */ '../views/RoomView.vue')
},{
path: '/stream/:id/:name/:streamer',
name: 'Streamer',
component: () => import(/* webpackChunkName: "Streamer" */ '../views/RoomView.vue')
},
{
path: '/remote/:id/:user',
name: 'remote',
component: () => import(/* webpackChunkName: "remote" */ '../views/remote.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router

@ -0,0 +1,17 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
token: null,
name: 'None'
},
mutations: {
},
actions: {
},
modules: {
}
})

@ -0,0 +1,720 @@
<template>
<v-container>
<v-row class="fill-height">
<v-col cols="24" md="1">
<v-card width="100%" dark class="fill-height">
<v-card-text>
<v-row>
<v-col>
<v-icon @click="addItemStart('text')">mdi-format-color-text</v-icon>
</v-col>
<v-col>
<v-icon @mousedown="addItemStart('rect')" @mouseup="clearStart()">
mdi-rectangle-outline
</v-icon>
</v-col>
<v-col>
<v-icon @mousedown="addItemStart('image')" @mouseup="clearStart()">mdi-image</v-icon>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
<v-col cols="24" md="9">
<v-card width="100%" min-height="600" dark class="fill-height">
<v-card-text color="white">
<div ref="vid" @click="handleAction" style="width: 100%;background: #fff;position: relative">
<div v-for="(track, index) in mainTracks" :key="index">
<div v-for="(keyframe, index) in track.keyframes" :key="index">
<div @click="selectTrack(track)"
v-if="keyframe.start <= currentTime && keyframe.end >=currentTime && track.type === 'text'"
@mouseenter="colorBorder(track)"
@mouseleave="colorBorderClear(track)" @mousedown="itemMoveStart"
@mouseup="itemMoveEnd" :ref="'item'+track.id" @dblclick="editText(track)"
:style="`padding:4px;color:${track.color};position:absolute;opacity:${vidTracks[track.id].opacity};top:${vidTracks[track.id].y-4}px;left:${vidTracks[track.id].x-4}px;font-size:${track.size};font-family:${track.font};border: 1px solid ${selectedTrack?(selectedTrack.id === track.id?'#ff0000':'transparent'):'transparent'}`"
>
<span :style="`line-height:${vidTracks[track.id].lineHeight}!important;letter-spacing:${vidTracks[track.id].letterSpacing}!important;`" v-html="track.text.replace(/\n/g, '<br>')"></span>
<div></div>
</div>
<div @click="selectTrack(track)" @mouseenter="colorBorder(track)"
v-if="keyframe.start <= currentTime && keyframe.end >=currentTime && track.type === 'image'"
@mouseleave="colorBorderClear(track)" @mousedown="itemMoveStart"
@mouseup="itemMoveEnd" :ref="'item'+track.id" @dblclick="editText(track)"
:style="`padding:4px;color:${track.color};position:absolute;opacity:${vidTracks[track.id].opacity};top:${vidTracks[track.id].y-4}px;left:${vidTracks[track.id].x-4}px;font-size:${track.size};font-family:${track.font};border: 1px solid ${selectedTrack?(selectedTrack.id === track.id?'#ff0000':'transparent'):'transparent'}`"
>
<img :src="track.image_data"
:width="vidTracks[track.id].width>0?vidTracks[track.id].width+'px':''"
:height="vidTracks[track.id].height>0?vidTracks[track.id].height+'px':''">
<div></div>
</div>
<div @click="selectTrack(track)" @mouseenter="colorBorder(track)"
v-if="keyframe.start <= currentTime && keyframe.end >=currentTime && track.type === 'rect'"
@mouseleave="colorBorderClear(track)" @mousedown="itemMoveStart"
@mouseup="itemMoveEnd" :ref="'item'+track.id" @dblclick="editText(track)"
:style="`padding:4px;color:${track.color};position:absolute;opacity:${vidTracks[track.id].opacity};top:${vidTracks[track.id].y-4}px;left:${vidTracks[track.id].x-4}px;font-size:${track.size};font-family:${track.font};border: 1px solid ${selectedTrack?(selectedTrack.id === track.id?'#ff0000':'transparent'):'transparent'}`"
>
<div :style="`width:${vidTracks[track.id].width}px;height:${vidTracks[track.id].height}px;background-color:${track.color}`"></div>
</div>
</div>
</div>
</div>
</v-card-text>
</v-card>
</v-col>
<v-col cols="24" md="2">
<v-card width="100%" dark class="fill-height">
<v-card-text>
<v-row>
<v-col v-if="selectedTrack">
<v-text-field v-if="selectedTrack.type === 'text'" label="Font Size"
placeholder="Font Size"
v-model="mainTracks[currentKeyTrack.track].size"
@keyup="mainTracks.__ob__.dep.notify()"></v-text-field>
<v-textarea v-if="selectedTrack.type === 'text'" label="Text"
@keyup="mainTracks.__ob__.dep.notify()" placeholder="Text"
v-model="mainTracks[currentKeyTrack.track].text"></v-textarea>
<v-text-field v-if="selectedTrack.type !== 'text'" label="Name"
@keyup="mainTracks.__ob__.dep.notify()" placeholder="Name"
v-model="mainTracks[currentKeyTrack.track].text"></v-text-field>
<v-menu
ref="menu"
v-model="colorMenu"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="290px"
v-if="selectedTrack.type !== 'image'"
>
<template v-slot:activator="{ on }">
<v-text-field
v-model="mainTracks[currentKeyTrack.track].color"
label="Color"
readonly
v-on="on"
></v-text-field>
</template>
<v-color-picker v-model="mainTracks[currentKeyTrack.track].color" no-title
scrollable show-swatches>
<v-spacer></v-spacer>
</v-color-picker>
</v-menu>
<v-text-field v-if="selectedTrack.type !== 'text'" label="Width"
@keyup="mainTracks.__ob__.dep.notify()" placeholder="Width"
v-model="mainTracks[currentKeyTrack.track].keyframes[currentKeyTrack.keyframe].width"></v-text-field>
<v-text-field v-if="selectedTrack.type === 'text'" label="Line Height"
@keyup="mainTracks.__ob__.dep.notify()" placeholder="Line Height"
v-model="mainTracks[currentKeyTrack.track].keyframes[currentKeyTrack.keyframe].lineHeight"></v-text-field>
<v-text-field v-if="selectedTrack.type === 'text'" label="Letter Spacing"
@keyup="mainTracks.__ob__.dep.notify()" placeholder="Letter Spacing"
v-model="mainTracks[currentKeyTrack.track].keyframes[currentKeyTrack.keyframe].letterSpacing"></v-text-field>
<v-text-field v-if="selectedTrack.type !== 'text'" label="Height"
@keyup="mainTracks.__ob__.dep.notify()" placeholder="Height"
v-model="mainTracks[currentKeyTrack.track].keyframes[currentKeyTrack.keyframe].height"></v-text-field>
<v-text-field v-if="selectedTrack.type === 'text'" label="Font"
@keyup="mainTracks.__ob__.dep.notify()" placeholder="Text"
v-model="mainTracks[currentKeyTrack.track].font"></v-text-field>
<v-text-field label="X" @keyup="mainTracks.__ob__.dep.notify()" placeholder="X"
v-model="mainTracks[currentKeyTrack.track].keyframes[currentKeyTrack.keyframe].x"></v-text-field>
<v-text-field label="Y" @keyup="mainTracks.__ob__.dep.notify()" placeholder="Y"
v-model="mainTracks[currentKeyTrack.track].keyframes[currentKeyTrack.keyframe].y"></v-text-field>
<v-text-field label="Opacity" @keyup="mainTracks.__ob__.dep.notify()"
placeholder="Opacity"
v-model="mainTracks[currentKeyTrack.track].keyframes[currentKeyTrack.keyframe].opacity"></v-text-field>
<v-btn small text @click="newKeyFrame">new keyframe</v-btn>
<v-btn small text @click="newMask">new mask</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="12">
<v-card width="100%" min-height="100" dark>
<v-card-text ref="videoPanel">
<div id="video_tracks" ref="vidPanelTracks" @mouseleave="allmoveend">
<div :ref="'vidtrack'+track.id" v-for="(track, index) in mainTracks" :key="index"
style="position: relative;padding: 3px">
<div @click="selectTrack(track)" @mousedown="startPositionTrack" @mouseup="allmoveend"
:style="`border:1px solid ${selectedTrack?(selectedTrack.id===track.id?'#f00':'#ccc'):'#ccc'};position: absolute;height:23px;top:${(index*23)}px;left:${((track.start/timeLength)*100)}%;width:${((track.end-track.start)/timeLength)*100}%`">
<div style="background: #ededed;right: 0px;top:0px;position: absolute;height: 22px;width: 5px;"
@mousedown="startChangeSizeTrack" @mouseup="allmoveend()"></div>
<div>{{ track.text }} ({{ (track.end - track.start).toFixed(2) }} sec)</div>
<div v-for="(keyframe , index) in track.keyframes" :key="index"
:style="`position:absolute;height:8px;width:8px;background:red;top:6px;margin-left:-4px;left:${(((keyframe.start - track.start)/track.end)*100)}%`"></div>
</div>
</div>
<div style="position: absolute;width: 5px; background: #aa00ff;height: 120px;top:3px;"
ref="currentTime" @mousedown="moveStartTime=true" @mouseup="moveStartTime=false">
</div>
</div>
</v-card-text>
<v-card-actions>
<v-text-field label="Total Secs" v-model="timeLength"></v-text-field>
<v-text-field label="Current" v-model="currentTime"></v-text-field>
<v-btn @click="preview">Start</v-btn>
<v-btn @click="previewStop">Stop</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
<div ref="vm">
<v-icon color="primary" :style="'position: absolute;left:' + moveX + 'px;top:'+ moveY + 'px'"
v-if="create==='text'">mdi-format-color-text
</v-icon>
<v-icon color="primary" :style="'position: absolute;left:' + moveX + 'px;top:'+ moveY + 'px'"
v-if="create==='image'">mdi-image
</v-icon>
<v-icon color="primary" :style="'position: absolute;left:' + moveX + 'px;top:'+ moveY + 'px'"
v-if="create==='rect'">mdi-rectangle-outline
</v-icon>
<div :style="`position: absolute;border:1px solid #ff0000;width:${width}px;height:${height}px;left:${moveX}px;top:${moveY}px`"
ref="rectcreate" v-if="create==='rect' && action==='paint'"></div>
<div :style="`position: absolute;border:1px solid #ff0000;width:${width}px;height:${height}px;left:${moveX}px;top:${moveY}px`"
ref="mask" v-if="oncreateMask && action==='paint'"></div>
</div>
</v-container>
</template>
<script>
export default {
name: "Animator",
data: () => ({
currentKeyTrack: {
keyframe: 0,
track: 0
},
vidTracks: {},
action: null,
item: null,
create: null,
index: 1,
colorPick: '',
timeLength: 60,
moveStartTime: false,
currentTime: 0,
oncreateMask: false,
pinterval: 0,
width: 0,
colorMenu: false,
heigth: 0,
clickOffset: {
x: 0,
y: 0
},
moveX: 0,
mainTracks: localStorage.getItem('animator')?JSON.parse(localStorage.getItem('animator')):[],
moveItem: false,
moveY: 0,
selectedTrack: null,
sizeChangeStart: false,
positionChangeStart: false,
editingItem: null
}),
mounted() {
this.$refs.vid.style.height = (this.$refs.vid.offsetWidth / (16 / 9)) + "px"
window.onmousemove = (event) => {
this.itemMove(event)
var bodyRect = document.body.getBoundingClientRect()
if (this.moveItem && this.currentKeyTrack) {
var elemRect = this.$refs.vid.getBoundingClientRect(),
offsetY = elemRect.top - bodyRect.top,
offsetX = elemRect.left - bodyRect.left
this.mainTracks[this.currentKeyTrack.track].y = (event.clientY - offsetY) - this.clickOffset.y
this.mainTracks[this.currentKeyTrack.track].x = (event.clientX - offsetX) - this.clickOffset.x
this.mainTracks[this.currentKeyTrack.track].keyframes[this.currentKeyTrack.keyframe].x = (event.clientX - offsetX) - this.clickOffset.x
this.mainTracks[this.currentKeyTrack.track].keyframes[this.currentKeyTrack.keyframe].y = (event.clientY - offsetY) - this.clickOffset.y
this.mainTracks.__ob__.dep.notify()
}
if (this.moveStartTime) {
console.log('')
var vidtrack = this.$refs.vidPanelTracks.getBoundingClientRect()
var offsetXVid = vidtrack.left - bodyRect.left
var pos = (event.clientX - offsetXVid + 12)
if (pos >= 15 && pos <= vidtrack.width + 15) {
this.$refs.currentTime.style.left = pos + "px"
console.log(offsetXVid, pos, vidtrack.width)
this.currentTime = (this.timeLength / vidtrack.width) * (pos - 15)
}
}
if (this.selectedTrack && this.positionChangeStart) {
var bodyRect3 = document.body.getBoundingClientRect(),
elemRect3 = this.$refs['vidtrack' + this.selectedTrack.id][0].getBoundingClientRect(),
offsetX3 = elemRect3.left - bodyRect3.left
var length2 = this.timeLength / 100
var totalLength2 = (((((event.clientX - this.clickOffset.x) - offsetX3)) / elemRect3.width) * length2) * 100
this.mainTracks.forEach(track => {
var tn = track.end - track.start
var originalstart = track.start
if (track.id === this.selectedTrack.id) {
track.start = totalLength2
track.end = track.start + tn
track.keyframes.forEach(keyframe => {
var tnx = track.end - track.start
keyframe.start = track.start + (originalstart - track.start)
keyframe.end = track.start + tnx
})
}
})
}
if (this.selectedTrack && this.sizeChangeStart) {
var bodyRect2 = document.body.getBoundingClientRect(),
elemRect2 = this.$refs['vidtrack' + this.selectedTrack.id][0].getBoundingClientRect(),
offsetX2 = elemRect2.left - bodyRect2.left
var length = this.timeLength / 100
var totalLength = ((((event.clientX - offsetX2) + 2) / elemRect2.width) * length) * 100
this.mainTracks.forEach(track => {
if (track.id === this.selectedTrack.id) track.end = totalLength
})
}
}
window.onkeyup = (event) => {
console.log(event.key)
if (event.target === document.body) {
if (event.key === 'ArrowDown' && this.selectedTrack) this.mainTracks[this.currentKeyTrack.track].keyframes[this.currentKeyTrack.keyframe].y++
if (event.key === 'ArrowUp' && this.selectedTrack) this.mainTracks[this.currentKeyTrack.track].keyframes[this.currentKeyTrack.keyframe].y--
if (event.key === 'ArrowLeft' && this.selectedTrack) this.mainTracks[this.currentKeyTrack.track].keyframes[this.currentKeyTrack.keyframe].x--
if (event.key === 'ArrowRight' && this.selectedTrack) this.mainTracks[this.currentKeyTrack.track].keyframes[this.currentKeyTrack.keyframe].x++
if (event.key === "Escape") {
this.selectedTrack = null
this.action = null
this.create = null
this.moveItem = false
this.sizeChangeStart = false
this.positionChangeStart = false
this.moveStartTime = false
this.previewStop()
}
}
}
},
watch: {
editingItem(newval, oldval) {
console.log(newval)
console.log('old', oldval)
// if (oldval !== null) {
// this.vidTracks.forEach(item => {
// if (item.id === newval.id) item = newval
// })
// }
},
mainTracks (newval) {
console.log('maintrack change')
localStorage.setItem('animator', JSON.stringify(newval))
},
currentTime(newval) {
if (newval) {
var videoPanel = this.$refs.vidPanelTracks.getBoundingClientRect()
var left = videoPanel.width / this.timeLength
if (!this.moveStartTime) this.$refs.currentTime.style.left = ((left * newval) + 15) + "px"
this.vidTracks = []
this.mainTracks.forEach((track, tindex) => {
if (track.start <= newval && newval <= track.end) {
var ttrack = track
var keyframe = null
var nextframe = null
track.keyframes.forEach((kvarey, index) => {
if (kvarey.start <= newval && newval <= kvarey.end) {
keyframe = kvarey
if (this.selectedTrack) {
if (this.selectedTrack.id === track.id) {
this.currentKeyTrack.keyframe = index
this.currentKeyTrack.track = tindex
}
}
}
})
ttrack.x = keyframe.x
ttrack.y = keyframe.y
ttrack.width = keyframe.width
ttrack.height = keyframe.height
ttrack.opacity = keyframe.opacity
var starter = null
track.keyframes.forEach((kvarey, index) => {
if (keyframe.end <= kvarey.start) {
if (kvarey.start < starter || starter === null) nextframe = index
if (starter === null) starter = kvarey.start
}
})
if (nextframe !== null) {
console.log(nextframe)
if (track.type === 'text') {
ttrack.letterSpacing = keyframe.letterSpacing
ttrack.lineHeight = keyframe.lineHeight
if (track.keyframes[nextframe].lineHeight !== ttrack.lineHeight) {
var difflineHeight = (track.keyframes[nextframe].lineHeight - ttrack.lineHeight)
difflineHeight = difflineHeight / (keyframe.end - keyframe.start)
difflineHeight = (newval - keyframe.start) * difflineHeight
ttrack.lineHeight = parseFloat(ttrack.lineHeight) + parseFloat(difflineHeight)
}
if (track.keyframes[nextframe].letterSpacing !== ttrack.letterSpacing) {
var diffletterSpacing = (track.keyframes[nextframe].letterSpacing - ttrack.letterSpacing)
diffletterSpacing = diffletterSpacing / (keyframe.end - keyframe.start)
diffletterSpacing = (newval - keyframe.start) * diffletterSpacing
ttrack.lineHeight = parseFloat(ttrack.letterSpacing) + parseFloat(diffletterSpacing)
}
}
if (track.keyframes[nextframe].x !== ttrack.x) {
var diffx = (track.keyframes[nextframe].x - ttrack.x)
diffx = diffx / (keyframe.end - keyframe.start)
diffx = (newval - keyframe.start) * diffx
ttrack.x = parseFloat(ttrack.x) + parseFloat(diffx)
}
if (track.keyframes[nextframe].width !== ttrack.width) {
var diffwidth = (track.keyframes[nextframe].width - ttrack.width)
diffwidth = diffwidth / (keyframe.end - keyframe.start)
diffwidth = (newval - keyframe.start) * diffwidth
ttrack.width = parseFloat(ttrack.width) + parseFloat(diffwidth)
}
if (track.keyframes[nextframe].height !== ttrack.height) {
console.log('heig', track.keyframes[nextframe].height, ttrack.height)
var diffheight = (track.keyframes[nextframe].height - ttrack.height)
diffheight = diffheight / (keyframe.end - keyframe.start)
diffheight = (newval - keyframe.start) * diffheight
ttrack.height = parseFloat(ttrack.height) + parseFloat(diffheight)
console.log(ttrack.height)
}
if (track.keyframes[nextframe].opacity !== ttrack.opacity) {
console.log('opa', track.keyframes[nextframe].opacity, ttrack.opacity)
var diffopacity = (track.keyframes[nextframe].opacity - ttrack.opacity)
diffopacity = parseFloat(diffopacity) / parseFloat(keyframe.end - keyframe.start)
console.log(diffopacity)
diffopacity = (newval - keyframe.start) * diffopacity
ttrack.opacity = parseFloat(ttrack.opacity) + parseFloat(diffopacity)
console.log(ttrack.opacity)
}
if (track.keyframes[nextframe].y !== ttrack.y) {
var diffy = (track.keyframes[nextframe].y - ttrack.y)
diffy = diffy / (keyframe.end - keyframe.start)
diffy = (newval - keyframe.start) * diffy
ttrack.y = parseFloat(ttrack.y) + parseFloat(diffy)
}
}
this.vidTracks[track.id] = ttrack
}
})
}
}
},
methods: {
newMask() {
this.oncreateMask = true
this.create = 'mask'
this.action = "create"
},
itemMoveStart(event) {
console.log(event)
this.moveItem = true
this.clickOffset.x = event.offsetX
this.clickOffset.y = event.offsetY
},
preview() {
this.pinterval = setInterval(() => {
this.currentTime += 1 / 60
if (this.currentTime >= 60) this.currentTime = 0
}, 1000 / 60)
},
previewStop() {
if (this.pinterval) clearInterval(this.pinterval)
},
newKeyFrame() {
this.mainTracks.forEach(track => {
if (track.id === this.selectedTrack.id) {
var pushFrame = null
var newFrames = []
var endTime = null
track.keyframes.forEach(keyframe => {
if (keyframe.start <= this.currentTime && keyframe.end >= this.currentTime) {
pushFrame = {
start: keyframe.start,
x: keyframe.x,
y: keyframe.y,
width: keyframe.width,
opacity: keyframe.opacity,
height: keyframe.height,
}
pushFrame.start = this.currentTime
keyframe.end = this.currentTime
}
if (pushFrame && pushFrame.start < keyframe.start) {
endTime = keyframe.start
}
newFrames.push(keyframe)
})
pushFrame.end = endTime
if (!pushFrame.end) pushFrame.end = track.end
newFrames.push(pushFrame)
track.keyframes = newFrames
}
})
},
selectTrack(track) {
if (this.selectedTrack === track) {
this.selectedTrack = null
return
}
this.selectedTrack = track
console.log(this.selectedTrack)
this.mainTracks.forEach((track, tindex) => {
if (this.selectedTrack.id === track.id) {
track.keyframes.forEach((kvarey, index) => {
if (kvarey.start <= this.currentTime && this.currentTime <= kvarey.end) {
this.currentKeyTrack.keyframe = index
this.currentKeyTrack.track = tindex
}
})
}
})
},
startChangeSizeTrack() {
this.sizeChangeStart = true
console.log("start size")
}
, startPositionTrack(event) {
if (!this.sizeChangeStart) this.positionChangeStart = true
console.log("start pos", event)
this.clickOffset.x = event.offsetX
},
deselectTrack() {
this.selectedTrack = null
this.sizeChangeStart = false
this.positionChangeStart = false
},
allmoveend() {
this.sizeChangeStart = false
this.positionChangeStart = false
},
colorBorderClear(track) {
var refItem = this.$refs['item' + track.id][0]
var id = ""
if (this.selectedTrack !== null) {
id = this.selectedTrack.id
}
if (id !== track.id) {
refItem.style.border = "1px solid transparent"
}
},
colorBorder(track) {
var refItem = this.$refs['item' + track.id][0]
var id = ""
if (this.selectedTrack !== null) {
id = this.selectedTrack.id
}
if (id !== track.id) {
refItem.style.border = "1px solid #ededed"
}
},
itemMoveEnd() {
this.moveItem = false
},
newTrack() {
this.tracks.push({})
},
editItem(item) {
console.log(item)
this.editingItem = item
var ref = this.$refs['item' + item.id][0]
console.log(ref)
// this.vidTracks.forEach(item => {
// var refItem = this.$refs['item' + item.id][0]
// refItem.style.border = "1px solid transparent"
//
// })
ref.style.border = "1px solid #000"
},
itemMove(event) {
if (this.action) {
if (this.action === 'create') {
this.moveX = event.clientX - 30
this.moveY = event.clientY - 30
}
if (this.action === 'paint') {
this.width = event.clientX - this.moveX
this.height = event.clientY - this.moveY
}
}
},
handleAction() {
console.log(this.action)
if (this.action === "create" || this.action === 'paint') {
var bodyRect = document.body.getBoundingClientRect(),
elemRect = this.$refs.vid.getBoundingClientRect(),
offsetY = elemRect.top - bodyRect.top,
offsetX = elemRect.left - bodyRect.left
if ((this.create === 'rect' || this.create === 'mask') && this.action === 'create') {
this.action = 'paint'
return
}
if (this.create === 'mask') {
this.mainTracks[this.currentKeyTrack.track].mask = {
x: this.moveX - offsetX,
y: this.moveY - offsetY,
width: this.width,
height: this.height,
}
this.mainTracks.__ob__.dep.notify()
this.oncreateMask = false
return
}
console.log(offsetY, offsetX)
var item = {
x: this.moveX - offsetX,
y: this.moveY - offsetY,
width: 0,
height: 0,
type: this.create,
opacity: 1,
index: this.index,
id: new Date().getTime(),
color: "#000",
mask: null
}
if (this.create === 'image') {
var upload = document.createElement('input')
upload.type = 'file'
upload.click()
upload.onchange = () => {
const reader = new FileReader();
reader.readAsDataURL(upload.files[0]);
reader.onloadend = () => {
item.image_data = reader.result
var image = new Image();
image.src = reader.result
image.onload = () => {
console.log(item)
this.index++
var maintrack = item
maintrack.keyframes = [{
x: item.x,
y: item.y,
width: image.width,
opacity: 1,
height: image.height,
start: this.currentTime,
end: this.timeLength,
lineHeight: 'default',
letterSpacing: 'default'
}]
maintrack.start = this.currentTime
maintrack.end = this.timeLength
this.mainTracks.push(maintrack)
this.vidTracks[item.id] = item
this.action = null
this.create = null
}
}
console.log(item)
console.log(upload.files)
}
} else {
console.log(item)
this.index++
var maintrack = item
maintrack.keyframes = [{
x: item.x,
y: item.y,
width: 0,
height: 0,
opacity: 1,
lineHeight: 'initial',
letterSpacing: 'initial',
start: this.currentTime,
end: this.timeLength
}]
if (this.create === 'rect') {
maintrack.keyframes[0].width = this.width
maintrack.keyframes[0].height = this.height
}
maintrack.start = this.currentTime
maintrack.end = this.timeLength
this.mainTracks.push(maintrack)
if (this.create === 'text') {
item.text = "Text"
item.font = "Arial"
item.size = '24px'
item.color = '#000'
item.align = 'left'
item.lineHeight = 'initial'
item.letterSpacing = 'initial'
}
this.vidTracks[item.id] = item
this.action = null
this.create = null
}
this.currentTime += 0.01
this.selectTrack(maintrack)
}
},
addItemStart(type) {
console.log(type)
this.create = type
this.action = 'create'
},
clearStart() {
this.create = null
},
getTracks() {
this.tracks.forEach(item => {
console.log(item)
})
}
}
}
</script>
<style scoped>
#video_edit {
width: 1280px;
height: 720px;
border: 1px solid #ccc;
}
#video_tracks {
min-height: 100px;
padding: 2px;
width: 100%;
border: 1px solid #ccc;
overflow-y: auto;
}
</style>

@ -0,0 +1,19 @@
<template>
<div>
<ObsVue server="ws://localhost:4444/"></ObsVue>
</div>
</template>
<script>
export default {
name: "Home",
data: () => ({
room: null
})
}
</script>
<style scoped>
</style>

@ -0,0 +1,83 @@
<template>
<v-container>
<v-dialog
v-model="createDialog"
max-width="290"
>
<v-card>
<v-card-title>
<v-row>
<v-col cols="12"><v-text-field v-model="roomCreate.room_name" placeholder="name"></v-text-field></v-col>
<v-col cols="12"><v-text-field v-model="roomCreate.publisher_count" placeholder="Room Publisher Count"></v-text-field></v-col>
<v-col cols="12"><v-select label="Video Codec" v-model="roomCreate.video_codec" :items="vcodecs"></v-select></v-col>
<v-col cols="12"><v-select label="Audio Codec" v-model="roomCreate.audio_codec" :items="acodecs"></v-select></v-col>
</v-row>
</v-card-title>
<v-card-actions>
<v-btn @click="createDialog=false">Cancel</v-btn>
<v-spacer></v-spacer>
<v-btn @click="saveRoom">Create</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-btn v-if="canCreate" @click="createDialog=true">Create Room</v-btn>
<v-row>
<v-col v-for="room in rooms" :key="room.id" cols="12">
<v-btn @click="joinRoom(room.ridn)" text>{{ room.room_name }}</v-btn>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: "Room",
data: () => ({
rooms: [],
canCreate: false,
createDialog: false,
vcodecs: ['VP8','VP9','h264'],
acodecs: ['OPUS'],
roomCreate: {
video_codec: 'VP9',
audio_codec: 'OPUS',
publisher_count: 16,
room_name: ''
}
}),
mounted () {
this.getRooms()
},
methods: {
saveRoom () {
this.$axios.post('create/room',this.roomCreate).then(response => {
this.roomCreate ={
video_codec: 'VP9',
audio_codec: 'OPUS',
publisher_count: 16,
room_name: ''
}
this.getRooms()
this.createDialog = false
console.log(response)
})
},
joinRoom (id) {
this.$router.push('/room/' + id)
},
getRooms() {
this.$axios.get('rooms').then(response => {
console.log(response)
this.rooms = response.data.rooms
this.canCreate = response.data.can_create
}).catch(() => {
this.$router.push('/')
})
}
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,81 @@
<template>
<div fill-height class="fill-height">
<ObsVue server="ws://localhost:4444/"></ObsVue>
<v-dialog
v-model="dialog"
max-width="290"
>
<v-card>
<v-card-title class="headline">
What's your name ?
</v-card-title>
<v-card-text>
<v-text-field label="Name" v-model="name"></v-text-field>
</v-card-text>
<v-card-actions>
<v-btn @click="dialog=false" text>Cancel</v-btn>
<v-spacer></v-spacer>
<v-btn @click="getRoom" color="primary" text>Enter Room</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<VueJanus @kick="kick" @rtp="rtp" :streamer="hasStreamer" v-if="rid" :creator="creator" :room="1234" server="ws://localhost:8188/ws" :username="name"></VueJanus>
</div>
</template>
<script>
import VueJanus from "@/lib/VueJanus";
// import ObsVue from "@/lib/ObsVue";
export default {
name: "RoomView",
components: {VueJanus},
// components: {ObsVue},
data: () => ({
rid:'',
name:'',
hasStreamer: false,
dialog:false,
creator:false
}),
created () {
console.log(this.$route.params)
this.name = this.$route.params.name
this.streamer = this.$route.params.streamer
this.hasStreamer = !!this.$route.params.streamer
},
mounted () {
console.log(this.$route.params)
this.name = this.$route.params.name
this.streamer = this.$route.params.streamer
this.hasStreamer = !!this.$route.params.streamer
if (this.name) this.getRoom()
if (!this.name) this.dialog = true
},
methods: {
kick (user) {
this.$axios.get(`kick/${this.$route.params.id}/${user}`).then(response => {
console.log(response)
})
},
rtp (user) {
this.$axios.get(`rtp/${this.$route.params.id}/${user}`).then(response => {
console.log(response)
})
},
getRoom () {
this.dialog = false
this.$axios.get(`room/${this.$route.params.id}`).then(response => {
this.rid = 1234
this.creator = response.data.can_modify
})
}
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,15 @@
<template>
<obs-vue server="ws://localhost:4444/"></obs-vue>
</template>
<script>
import ObsVue from "@/lib/ObsVue";
export default {
name: "StreamControl",
components: {ObsVue}
}
</script>
<style scoped>
</style>

@ -0,0 +1,47 @@
<template>
<v-container>
<v-row>
<v-col cols="12" md="12" sm="12" v-if="error">
<v-alert type="error">{{error}}</v-alert>
</v-col>
<v-col cols="12" md="6" sm="12">
<v-text-field placeholder="Code" v-model="code"></v-text-field>
</v-col>
<v-col cols="12" md="6" sm="12">
<v-text-field v-model="password" placeholder="Password" type="password"></v-text-field>
</v-col>
<v-col cols="12" md="6" sm="12">
<v-btn @click="login">Login</v-btn>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'Home',
data:() => ({
passwd: false,
name:'',
code:'',
error:'',
password:''
}),
methods: {
login () {
this.$axios.post('login', {
username: this.code,
password: this.password
}).then(response => {
this.$store.state.token = response.data.access_token
this.$axios.defaults.headers.common['Authorization'] = "Bearer " + response.data.access_token
this.$store.state.name = this.name
this.$router.push('/rooms')
}).catch((resp) => {
this.error = resp.response.data.msg
})
}
}
}
</script>

@ -0,0 +1,52 @@
<template>
<div style="width: 1280px; height: 720px">
<RemoteFeedOnly @kick="kick" @rtp="rtp" v-if="rid" :creator="creator" :user="$route.params.user" :room="rid" server="ws://localhost:8188/ws" :username="name"></RemoteFeedOnly>
<!-- <ObsVue server="ws://localhost:4444/"></ObsVue>-->
</div>
</template>
<script>
// import VueJanus from "@/lib/VueJanus";
import RemoteFeedOnly from "@/lib/RemoteFeedOnly";
export default {
name: "RoomView",
// components: {VueJanus},
components: {RemoteFeedOnly},
data: () => ({
rid:'',
name:'',
dialog:false,
creator:false
}),
mounted () {
// if (!name) this.dialog = true
this.getRoom()
},
methods: {
kick (user) {
this.$axios.get(`kick/${this.$route.params.id}/${user}`).then(response => {
console.log(response)
})
},
rtp (user) {
this.$axios.get(`rtp/${this.$route.params.id}/${user}`).then(response => {
console.log(response)
})
},
getRoom () {
this.dialog = false
this.$axios.get(`room/${this.$route.params.id}`).then(response => {
this.rid = response.data.rid
this.creator = response.data.can_modify
})
}
}
}
</script>
<style scoped>
body , html, div {
overflow: hidden;
}
</style>
Loading…
Cancel
Save