forked from sagi/seepur
book reading PoC
This commit is contained in:
parent
21ef1e25b2
commit
74ed7612d6
14 changed files with 144 additions and 23 deletions
|
@ -45,6 +45,10 @@ class ClientApiController {
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBooks() {}
|
||||||
|
|
||||||
|
async getCallBooks() {}
|
||||||
|
|
||||||
async createCall({auth, request, response}) {
|
async createCall({auth, request, response}) {
|
||||||
try {
|
try {
|
||||||
const user = auth.user;
|
const user = auth.user;
|
||||||
|
|
|
@ -93,6 +93,8 @@ class CallSession {
|
||||||
socket.on('wrtc:sdp:offer', this.onSdpOffer.bind(this));
|
socket.on('wrtc:sdp:offer', this.onSdpOffer.bind(this));
|
||||||
socket.on('wrtc:sdp:answer', this.onSdpAnswer.bind(this));
|
socket.on('wrtc:sdp:answer', this.onSdpAnswer.bind(this));
|
||||||
socket.on('wrtc:ice', this.onIceCandidate.bind(this));
|
socket.on('wrtc:ice', this.onIceCandidate.bind(this));
|
||||||
|
socket
|
||||||
|
.on('book:action:flip-page', this.onActionBookFlip.bind(this))
|
||||||
await this.updateState();
|
await this.updateState();
|
||||||
if (this.state === 'STARTED') {
|
if (this.state === 'STARTED') {
|
||||||
await this.sendStandby(socket, userIndex);
|
await this.sendStandby(socket, userIndex);
|
||||||
|
@ -160,14 +162,14 @@ class CallSession {
|
||||||
const {from = payload.id, ice} = payload;
|
const {from = payload.id, ice} = payload;
|
||||||
const to = from === 1 ? 2 : 1;
|
const to = from === 1 ? 2 : 1;
|
||||||
this[`user_${to}`].socket.emit('wrtc:ice', {ice});
|
this[`user_${to}`].socket.emit('wrtc:ice', {ice});
|
||||||
console.log(`[Signal] [onIceCandidate] ${from} -> ${to}`)
|
console.log(`[Signal] [onIceCandidate] ${from} -> ${to}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async onSdpOffer(payload) {
|
async onSdpOffer(payload) {
|
||||||
const {from = payload.id, sdp} = payload;
|
const {from = payload.id, sdp} = payload;
|
||||||
const to = from === 1 ? 2 : 1;
|
const to = from === 1 ? 2 : 1;
|
||||||
this[`user_${to}`].socket.emit('wrtc:sdp:offer', {sdp});
|
this[`user_${to}`].socket.emit('wrtc:sdp:offer', {sdp});
|
||||||
console.log(`[Signal] [onSdpOffer] ${from} -> ${to}`)
|
console.log(`[Signal] [onSdpOffer] ${from} -> ${to}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +177,16 @@ class CallSession {
|
||||||
const {from = payload.id, sdp} = payload;
|
const {from = payload.id, sdp} = payload;
|
||||||
const to = from === 1 ? 2 : 1;
|
const to = from === 1 ? 2 : 1;
|
||||||
this[`user_${to}`].socket.emit('wrtc:sdp:answer', {sdp});
|
this[`user_${to}`].socket.emit('wrtc:sdp:answer', {sdp});
|
||||||
console.log(`[Signal] [onSdpAnswer] ${from} -> ${to}`)
|
console.log(`[Signal] [onSdpAnswer] ${from} -> ${to}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onActionBookFlip(payload) {
|
||||||
|
const {from = payload.id, direction} = payload;
|
||||||
|
const to = from === 1 ? 2 : 1;
|
||||||
|
this[`user_${to}`].socket.emit('book:action:flip-page', {direction});
|
||||||
|
console.log(
|
||||||
|
`[Signal] [book] [action] [flip] [${direction}] ${from} -> ${to}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
app/Models/Book.js
Normal file
12
app/Models/Book.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
|
||||||
|
const Model = use('Model')
|
||||||
|
|
||||||
|
class Book extends Model {
|
||||||
|
user() {
|
||||||
|
return this.belongsTo('App/Models/User');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Book
|
22
database/migrations/1586821478599_book_schema.js
Normal file
22
database/migrations/1586821478599_book_schema.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/** @type {import('@adonisjs/lucid/src/Schema')} */
|
||||||
|
const Schema = use('Schema')
|
||||||
|
|
||||||
|
class BookSchema extends Schema {
|
||||||
|
up() {
|
||||||
|
this.create('books', (table) => {
|
||||||
|
table.increments()
|
||||||
|
table.bigInteger('user_id').notNullable();
|
||||||
|
table.integer('pages').notNullable();
|
||||||
|
table.string('book_folder').notNullable();
|
||||||
|
table.timestamps()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
down() {
|
||||||
|
this.drop('books')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BookSchema
|
|
@ -39,6 +39,7 @@
|
||||||
"adonis-vue-websocket": "^2.0.2",
|
"adonis-vue-websocket": "^2.0.2",
|
||||||
"animate.css": "^3.7.2",
|
"animate.css": "^3.7.2",
|
||||||
"bulma": "^0.8.0",
|
"bulma": "^0.8.0",
|
||||||
|
"flipbook-vue": "^0.8.1",
|
||||||
"fork-awesome": "^1.1.7",
|
"fork-awesome": "^1.1.7",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"regenerator-runtime": "^0.13.5",
|
"regenerator-runtime": "^0.13.5",
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -6914,3 +6914,7 @@ label.panel-block {
|
||||||
opacity: 0; }
|
opacity: 0; }
|
||||||
100% {
|
100% {
|
||||||
opacity: 1; } }
|
opacity: 1; } }
|
||||||
|
|
||||||
|
.flipbook {
|
||||||
|
width: 100%;
|
||||||
|
height: 60vh; }
|
||||||
|
|
|
@ -223,3 +223,8 @@ $positions: (
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: Remove this - make generic
|
||||||
|
.flipbook {
|
||||||
|
width: 100%;
|
||||||
|
height: 60vh;
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import WebSocketService from "../scripts/websocket.service";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
export default class CallManager {
|
export default class CallManager {
|
||||||
private inCall: boolean;
|
private inCall: boolean;
|
||||||
private peerId: number;
|
public peerId: number;
|
||||||
private localStream: MediaStream;
|
private localStream: MediaStream;
|
||||||
private remoteStream: MediaStream;
|
private remoteStream: MediaStream;
|
||||||
private signalingChannel;
|
private signalingChannel;
|
||||||
|
@ -16,6 +16,7 @@ export default class CallManager {
|
||||||
this.remoteStream = new MediaStream();
|
this.remoteStream = new MediaStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async connectToCall(mediaConstraints: MediaStreamConstraints): Promise<boolean> {
|
async connectToCall(mediaConstraints: MediaStreamConstraints): Promise<boolean> {
|
||||||
if (this.inCall) throw new Error('Already connected to call');
|
if (this.inCall) throw new Error('Already connected to call');
|
||||||
console.log('connecting to call');
|
console.log('connecting to call');
|
||||||
|
@ -30,6 +31,7 @@ export default class CallManager {
|
||||||
signalingChannel.on('wrtc:sdp:offer', self.onRemoteOffer.bind(self));
|
signalingChannel.on('wrtc:sdp:offer', self.onRemoteOffer.bind(self));
|
||||||
signalingChannel.on('wrtc:sdp:answer', self.onRemoteAnswer.bind(self));
|
signalingChannel.on('wrtc:sdp:answer', self.onRemoteAnswer.bind(self));
|
||||||
signalingChannel.on('wrtc:ice', self.onRemoteIce.bind(self));
|
signalingChannel.on('wrtc:ice', self.onRemoteIce.bind(self));
|
||||||
|
signalingChannel.on('book:action:flip-page', self.onActionBookFlip.bind(self))
|
||||||
signalingChannel.on('error', (e) => {
|
signalingChannel.on('error', (e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
resolve(false)
|
resolve(false)
|
||||||
|
@ -150,6 +152,10 @@ export default class CallManager {
|
||||||
return this.remoteStream;
|
return this.remoteStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onActionBookFlip(payload) {
|
||||||
|
this.emit(ECallEvents.ACTION_BOOK_FLIP, payload);
|
||||||
|
};
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
console.log('Closing...');
|
console.log('Closing...');
|
||||||
if (!this.inCall) return;
|
if (!this.inCall) return;
|
||||||
|
@ -162,9 +168,11 @@ export default class CallManager {
|
||||||
this.remoteStream = null;
|
this.remoteStream = null;
|
||||||
this.inCall = false;
|
this.inCall = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ECallEvents {
|
export enum ECallEvents {
|
||||||
CLOSE = 'CLOSE',
|
CLOSE = 'CLOSE',
|
||||||
REMOTE_STREAM = 'REMOTE_STREAM'
|
REMOTE_STREAM = 'REMOTE_STREAM',
|
||||||
|
ACTION_BOOK_FLIP = 'ACTION_BOOK_FLIP'
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
<div v-if="loading">
|
<div v-if="loading">
|
||||||
<Loading />
|
<Loading />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="is-flex">
|
<div v-else class>
|
||||||
|
<div class="video-strip is-flex">
|
||||||
<video
|
<video
|
||||||
:src-object.prop.camel="localStream"
|
:src-object.prop.camel="localStream"
|
||||||
autoplay
|
autoplay
|
||||||
|
@ -13,6 +14,18 @@
|
||||||
/>
|
/>
|
||||||
<video :src-object.prop.camel="remoteStream" autoplay style="max-width:40%" />
|
<video :src-object.prop.camel="remoteStream" autoplay style="max-width:40%" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class>
|
||||||
|
<flipbook
|
||||||
|
class="flipbook"
|
||||||
|
:pages="createPages(book)"
|
||||||
|
forwardDirection="left"
|
||||||
|
:zooms="null"
|
||||||
|
@flip-left-end="onFlip('left')"
|
||||||
|
@flip-right-end="onFlip('right')"
|
||||||
|
ref="flipbook"
|
||||||
|
></flipbook>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -20,11 +33,13 @@
|
||||||
import Loading from "../../shared/components/Loading/Loading.vue";
|
import Loading from "../../shared/components/Loading/Loading.vue";
|
||||||
import WebsocketService from "../scripts/websocket.service";
|
import WebsocketService from "../scripts/websocket.service";
|
||||||
import CallManager, { ECallEvents } from "../classes/call.manager";
|
import CallManager, { ECallEvents } from "../classes/call.manager";
|
||||||
|
import Flipbook from "flipbook-vue";
|
||||||
import Services from "../../services/index";
|
import Services from "../../services/index";
|
||||||
import { mapActions, mapGetters } from "vuex";
|
import { mapActions, mapGetters } from "vuex";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Loading
|
Loading,
|
||||||
|
Flipbook
|
||||||
},
|
},
|
||||||
name: "Call",
|
name: "Call",
|
||||||
|
|
||||||
|
@ -35,6 +50,7 @@ export default {
|
||||||
const ws = await WebsocketService.getInstance();
|
const ws = await WebsocketService.getInstance();
|
||||||
this.callManager = new CallManager(ws, callId);
|
this.callManager = new CallManager(ws, callId);
|
||||||
this.callManager.on(ECallEvents.CLOSE, this.callEnded);
|
this.callManager.on(ECallEvents.CLOSE, this.callEnded);
|
||||||
|
this.callManager.on(ECallEvents.ACTION_BOOK_FLIP, this.onRemoteFlip);
|
||||||
const success = await this.callManager.connectToCall({
|
const success = await this.callManager.connectToCall({
|
||||||
video: true,
|
video: true,
|
||||||
audio: true
|
audio: true
|
||||||
|
@ -63,6 +79,27 @@ export default {
|
||||||
async setupCall(): Promise<boolean> {
|
async setupCall(): Promise<boolean> {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
onFlip(direction: "left" | "right") {
|
||||||
|
if (this.callManager.peerId == 2)
|
||||||
|
this.callManager.send(`book:action:flip-page`, { direction });
|
||||||
|
},
|
||||||
|
onRemoteFlip(payload) {
|
||||||
|
switch (payload.direction) {
|
||||||
|
case "left":
|
||||||
|
this.$refs.flipbook.flipLeft();
|
||||||
|
break;
|
||||||
|
case "right":
|
||||||
|
this.$refs.flipbook.flipRight();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createPages(book) {
|
||||||
|
const pages = [null];
|
||||||
|
for (let i = 1; i < book.pages + 1; i++) {
|
||||||
|
pages.push(`/u/images/${i}.JPG`);
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
},
|
||||||
callEnded(callId) {
|
callEnded(callId) {
|
||||||
this.notify({ message: `Call #${callId} Ended` });
|
this.notify({ message: `Call #${callId} Ended` });
|
||||||
this.$router.push({ path: `/` });
|
this.$router.push({ path: `/` });
|
||||||
|
@ -74,6 +111,10 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
book: {
|
||||||
|
title: "Taylor",
|
||||||
|
pages: 34
|
||||||
|
},
|
||||||
loading: true,
|
loading: true,
|
||||||
localStream: null,
|
localStream: null,
|
||||||
remoteStream: null,
|
remoteStream: null,
|
||||||
|
@ -84,3 +125,4 @@ export default {
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -3142,6 +3142,13 @@ flagged-respawn@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41"
|
resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41"
|
||||||
integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==
|
integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==
|
||||||
|
|
||||||
|
flipbook-vue@^0.8.1:
|
||||||
|
version "0.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/flipbook-vue/-/flipbook-vue-0.8.1.tgz#1b3d29a5a8d0d6921ff0be1a91e4165b0d9d6d6e"
|
||||||
|
integrity sha512-HPWrGe6fwIGSG2IV+kN2Bz16pp8DtsgvNrObxaXtr+XNwxgaLWpbKzM8QV5alBcMFJtyo+7d2Ds1zLrCp8h1LQ==
|
||||||
|
dependencies:
|
||||||
|
rematrix "^0.4.1"
|
||||||
|
|
||||||
flush-write-stream@^1.0.0:
|
flush-write-stream@^1.0.0:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
|
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
|
||||||
|
@ -5993,6 +6000,11 @@ regjsparser@^0.1.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
jsesc "~0.5.0"
|
jsesc "~0.5.0"
|
||||||
|
|
||||||
|
rematrix@^0.4.1:
|
||||||
|
version "0.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/rematrix/-/rematrix-0.4.1.tgz#496d40b48c79dd3820bc40352b8d7bec44b32887"
|
||||||
|
integrity sha512-nH4OjL09zw4yhdBClK4B93kRWbHu+std7ZV6ljS2Zsgy2YrPcayL67axKLfMxoFool8mPyVY614m8SFE7sJQLw==
|
||||||
|
|
||||||
remove-trailing-separator@^1.0.1:
|
remove-trailing-separator@^1.0.1:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||||
|
|
Loading…
Reference in a new issue