so I am trying to mimic this code pen https://codepen.io/airnan/pen/ZLVJmq but when I try to load the same animation in with lottie-web npm package everything works great until the last animation. Then it all gets messed up. I'm trying to figure it out but I am so confused. Any ideas as to why? The JSON file is exactly the same except for text_data variable. I just import the whole thing and access it directly. Heres the code. it's just the last frame that doesn't work. it doesn't animate the text at all instead it looks like this.
<template>
<div>
<button #click="render">Render</button>
<h2>Backgrounds</h2>
<template v-for="background in backgrounds">
<img
:src="background.poster"
class="backgrounds"
#click="changeBackground(background.video)"
/>
</template>
<h2>Images</h2>
<template v-for="image in images">
<img
:src="image.source"
#click="addImage(image.source, image.type || null)"
class="images"
/>
</template>
<br />
<button #click="addText">Add Text</button>
<button v-if="selectedNode" #click="removeNode">
Remove selected {{ selectedNode.type }}
</button>
<label>Font:</label>
<select v-model="selectedFont">
<option value="Arial">Arial</option>
<option value="Courier New">Courier New</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Desoto">Desoto</option>
<option value="Kalam">Kalam</option>
</select>
<label>Font Size</label>
<input type="number" v-model="selectedFontSize" />
<label>Font Style:</label>
<select v-model="selectedFontStyle">
<option value="normal">Normal</option>
<option value="bold">Bold</option>
<option value="italic">Italic</option>
</select>
<label>Color:</label>
<input type="color" v-model="selectedColor" />
<button
v-if="selectedNode && selectedNode.type === 'text'"
#click="updateText"
>
Update Text
</button>
<template v-if="selectedNode && selectedNode.lottie">
<input type="text" v-model="text">
<button #click="updateAnim(selectedNode.lottie.imgSrc, 'anim')">
Update Animation
</button>
</template>
<br />
<video
id="preview"
v-show="preview"
:src="preview"
:width="width"
:height="height"
preload="auto"
controls
/>
<a v-if="file" :href="file" download="dopeness.mp4">download</a>
<div id="container"></div>
</div>
</template>
<script>
import lottie from "lottie-web";
import * as animationData from "../data.json";
animationData.layers[0].t.d.k[0].s.t = "text";
animationData.layers[1].t.d.k[0].s.t = "text";
animationData.layers[2].t.d.k[0].s.t = "text";
animationData.layers[3].t.d.k[0].s.t = "text";
animationData.layers[4].t.d.k[0].s.t = "text";
export default {
data() {
return {
source: null,
stage: null,
layer: null,
video: null,
animations: [],
text: "",
animationData: animationData.default,
captures: [],
backgrounds: [
{
poster: "/api/files/stock/3oref310k1uud86w/poster/poster.jpg",
video:
"/api/files/stock/3oref310k1uud86w/main/1080/3oref310k1uud86w_1080.mp4"
},
{
poster: "/api/files/stock/3yj2e30tk5x6x0ww/poster/poster.jpg",
video:
"/api/files/stock/3yj2e30tk5x6x0ww/main/1080/3yj2e30tk5x6x0ww_1080.mp4"
},
{
poster: "/api/files/stock/2ez931ik1mggd6j/poster/poster.jpg",
video:
"/api/files/stock/2ez931ik1mggd6j/main/1080/2ez931ik1mggd6j_1080.mp4"
},
{
poster: "/api/files/stock/yxrt4ej4jvimyk15/poster/poster.jpg",
video:
"/api/files/stock/yxrt4ej4jvimyk15/main/1080/yxrt4ej4jvimyk15_1080.mp4"
},
{
poster:
"https://images.costco-static.com/ImageDelivery/imageService?profileId=12026540&itemId=100424771-847&recipeName=680",
video: "/api/files/jedi/surfing.mp4"
},
{
poster:
"https://thedefensepost.com/wp-content/uploads/2018/04/us-soldiers-afghanistan-4308413-1170x610.jpg",
video: "/api/files/jedi/soldiers.mp4"
}
],
images: [
{ source: "/api/files/jedi/solo.jpg" },
{ source: "api/files/jedi/yoda.jpg" },
{ source: "api/files/jedi/yodaChristmas.jpg" },
{ source: "api/files/jedi/darthMaul.jpg" },
{ source: "api/files/jedi/darthMaul1.jpg" },
{ source: "api/files/jedi/trump.jpg" },
{ source: "api/files/jedi/hat.png" },
{ source: "api/files/jedi/trump.png" },
{ source: "api/files/jedi/bernie.png" },
{ source: "api/files/jedi/skywalker.png" },
{ source: "api/files/jedi/vader.gif" },
{ source: "api/files/jedi/vader2.gif" },
{ source: "api/files/jedi/yoda.gif" },
{ source: "api/files/jedi/kylo.gif" },
{
source: "https://media3.giphy.com/media/R3IxJW14a3QNa/source.gif",
type: "anim"
}
],
backgroundVideo: null,
imageGroups: [],
anim: null,
selectedNode: null,
selectedFont: "Arial",
selectedColor: "black",
selectedFontSize: 20,
selectedFontStyle: "normal",
width: 1920,
height: 1080,
texts: [],
preview: null,
file: null,
canvas: null
};
},
mounted: function() {
this.initCanvas();
},
methods: {
changeBackground(source) {
this.source = source;
this.video.src = this.source;
this.anim.stop();
this.anim.start();
this.video.play();
},
removeNode() {
if (this.selectedNode && this.selectedNode.type === "text") {
this.selectedNode.transformer.destroy(
this.selectedNode.text.transformer
);
this.selectedNode.text.destroy(this.selectedNode.text);
this.texts.splice(this.selectedNode.text.index - 1, 1);
this.selectedNode = null;
this.layer.draw();
} else if (this.selectedNode && this.selectedNode.type == "image") {
this.selectedNode.group.destroy(this.selectedNode);
this.imageGroups.splice(this.selectedNode.group.index - 1, 1);
if (this.selectedNode.lottie) {
clearTimeout(this.animations.interval);
this.selectedNode.lottie.destroy();
this.animations.splice(this.selectedNode.lottie.index - 1, 1);
}
this.selectedNode = null;
this.layer.draw();
}
},
async addImage(src, anim, isUpdate) {
let lottieAnimation = null;
let imageObj = null;
const type = anim || src.slice(src.lastIndexOf("."));
const vm = this;
function process(img) {
return new Promise((resolve, reject) => {
img.onload = () => resolve({ width: img.width, height: img.height });
});
}
imageObj = new Image();
imageObj.src = src;
imageObj.width = 200;
imageObj.height = 200;
await process(imageObj);
if (type === ".gif") {
const canvas = document.createElement("canvas");
canvas.setAttribute("id", "gif");
async function onDrawFrame(ctx, frame) {
ctx.drawImage(frame.buffer, frame.x, frame.y);
// redraw the layer
vm.layer.draw();
}
gifler(src).frames(canvas, onDrawFrame);
canvas.onload = async () => {
canvas.parentNode.removeChild(canvas);
};
imageObj = canvas;
const gif = new Image();
gif.src = src;
const gifImage = await process(gif);
imageObj.width = gifImage.width;
imageObj.height = gifImage.height;
} else if (type === "anim") {
if(!isUpdate){this.text = "new text";}
const canvas = document.createElement("canvas");
canvas.style.height = 200;
canvas.style.width = 200;
canvas.setAttribute("id", "animationCanvas");
const ctx = canvas.getContext("2d");
const div = document.createElement("div");
div.setAttribute("id", "animationContainer");
div.style.display = "none";
canvas.style.display = "none";
this.animationData.layers[0].t.d.k[0].s.t = this.text;
this.animationData.layers[1].t.d.k[0].s.t = this.text;
this.animationData.layers[2].t.d.k[0].s.t = this.text;
this.animationData.layers[3].t.d.k[0].s.t = this.text;
this.animationData.layers[4].t.d.k[0].s.t = this.text;
lottieAnimation = lottie.loadAnimation({
container: div, // the dom element that will contain the animation
renderer: "svg",
loop: true,
autoplay: true,
animationData: this.animationData
});
lottieAnimation.imgSrc = src;
lottieAnimation.text = this.text;
const timer = setInterval(async () => {
const svg = await div.getElementsByTagName("svg")[0];
const xml = new XMLSerializer().serializeToString(svg);
const svg64 = window.btoa(xml);
const b64Start = "data:image/svg+xml;base64,";
const image64 = b64Start + svg64;
imageObj = new Image({ width: 200, height: 200 });
imageObj.src = image64;
await process(imageObj);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(imageObj, 0, 0, canvas.width, canvas.height);
this.layer.draw();
}, 1000 / 30);
this.animations.push({ lottie: lottieAnimation, interval: timer });
imageObj = canvas;
canvas.onload = async () => {
canvas.parentNode.removeChild(canvas);
};
}
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
width: imageObj.width,
height: imageObj.height,
position: (0, 0),
strokeWidth: 10,
stroke: "blue",
strokeEnabled: false
});
const group = new Konva.Group({
draggable: true
});
// add the shape to the layer
addAnchor(group, 0, 0, "topLeft");
addAnchor(group, imageObj.width, 0, "topRight");
addAnchor(group, imageObj.width, imageObj.height, "bottomRight");
addAnchor(group, 0, imageObj.height, "bottomLeft");
imageObj = null;
image.on("click", function () {
vm.hideAllHelpers();
vm.selectedNode = {
type: "image",
group,
lottie: lottieAnimation
};
if(lottieAnimation && lottieAnimation.text){vm.text = lottieAnimation.text}
group.find("Circle").show();
vm.layer.draw();
});
image.on("mouseover", function(evt) {
if (vm.selectedNode && vm.selectedNode.type === "image") {
const index = image.getParent().index;
const groupId = vm.selectedNode.group.index;
if (index != groupId) {
evt.target.strokeEnabled(true);
vm.layer.draw();
}
} else {
evt.target.strokeEnabled(true);
vm.layer.draw();
}
});
image.on("mouseout", function(evt) {
evt.target.strokeEnabled(false);
vm.layer.draw();
});
vm.hideAllHelpers();
group.find("Circle").show();
group.add(image);
vm.layer.add(group);
vm.imageGroups.push(group);
vm.selectedNode = {
type: "image",
group,
lottie: lottieAnimation
};
vm.layer.draw();
function update(activeAnchor) {
const group = activeAnchor.getParent();
let topLeft = group.get(".topLeft")[0];
let topRight = group.get(".topRight")[0];
let bottomRight = group.get(".bottomRight")[0];
let bottomLeft = group.get(".bottomLeft")[0];
let image = group.get("Image")[0];
let anchorX = activeAnchor.getX();
let anchorY = activeAnchor.getY();
// update anchor positions
switch (activeAnchor.getName()) {
case "topLeft":
topRight.y(anchorY);
bottomLeft.x(anchorX);
break;
case "topRight":
topLeft.y(anchorY);
bottomRight.x(anchorX);
break;
case "bottomRight":
bottomLeft.y(anchorY);
topRight.x(anchorX);
break;
case "bottomLeft":
bottomRight.y(anchorY);
topLeft.x(anchorX);
break;
}
image.position(topLeft.position());
let width = topRight.getX() - topLeft.getX();
let height = bottomLeft.getY() - topLeft.getY();
if (width && height) {
image.width(width);
image.height(height);
}
}
function addAnchor(group, x, y, name) {
let stage = vm.stage;
let layer = vm.layer;
let anchor = new Konva.Circle({
x: x,
y: y,
stroke: "#666",
fill: "#ddd",
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
anchor.on("dragmove", function() {
update(this);
layer.draw();
});
anchor.on("mousedown touchstart", function() {
group.draggable(false);
this.moveToTop();
});
anchor.on("dragend", function() {
group.draggable(true);
layer.draw();
});
// add hover styling
anchor.on("mouseover", function() {
let layer = this.getLayer();
document.body.style.cursor = "pointer";
this.strokeWidth(4);
layer.draw();
});
anchor.on("mouseout", function() {
let layer = this.getLayer();
document.body.style.cursor = "default";
this.strokeWidth(2);
layer.draw();
});
group.add(anchor);
}
},
async updateAnim(src, type){
this.addImage(src, type, true);
this.removeNode();
},
hideAllHelpers() {
for (let i = 0; i < this.texts.length; i++) {
this.texts[i].transformer.hide();
}
for (let b = 0; b < this.imageGroups.length; b++) {
this.imageGroups[b].find("Circle").hide();
}
},
async startRecording(duration) {
const chunks = []; // here we will store our recorded media chunks (Blobs)
const stream = this.canvas.captureStream(30); // grab our canvas MediaStream
const rec = new MediaRecorder(stream, {
videoBitsPerSecond: 20000 * 1000
});
// every time the recorder has new data, we will store it in our array
rec.ondataavailable = e => chunks.push(e.data);
// only when the recorder stops, we construct a complete Blob from all the chunks
rec.onstop = async e => {
this.anim.stop();
const blob = new Blob(chunks, {
type: "video/webm"
});
this.preview = await URL.createObjectURL(blob);
const video = window.document.getElementById("preview");
const previewVideo = new Konva.Image({
image: video,
draggable: false,
width: this.width,
height: this.height
});
this.layer.add(previewVideo);
console.log("video", video);
video.addEventListener("ended", () => {
console.log("preview ended");
if (!this.file) {
const vid = new Whammy.fromImageArray(this.captures, 30);
this.file = URL.createObjectURL(vid);
}
previewVideo.destroy();
this.anim.stop();
this.anim.start();
this.video.play();
});
let seekResolve;
video.addEventListener("seeked", async () => {
if (seekResolve) seekResolve();
});
video.addEventListener("loadeddata", async () => {
let interval = 1 / 30;
let currentTime = 0;
while (currentTime <= duration && !this.file) {
video.currentTime = currentTime;
await new Promise(r => (seekResolve = r));
this.layer.draw();
let base64ImageData = this.canvas.toDataURL("image/webp");
this.captures.push(base64ImageData);
currentTime += interval;
video.currentTime = currentTime;
}
this.layer.draw();
});
};
rec.start();
setTimeout(() => rec.stop(), duration);
},
async render() {
this.captures = [];
this.preview = null;
this.file = null;
console.log(this.captures.length);
this.hideAllHelpers();
this.selectedNode = null;
this.video.currentTime = 0;
this.video.loop = false;
const duration = this.video.duration * 1000;
this.startRecording(duration);
this.layer.draw();
},
updateText() {
if (this.selectedNode && this.selectedNode.type === "text") {
const text = this.selectedNode.text;
const transformer = this.selectedNode.transformer;
text.fontSize(this.selectedFontSize);
text.fontFamily(this.selectedFont);
text.fontStyle(this.selectedFontStyle);
text.fill(this.selectedColor);
this.layer.draw();
}
},
addText() {
const vm = this;
const text = new Konva.Text({
text: "new text " + (vm.texts.length + 1),
x: 50,
y: 80,
fontSize: this.selectedFontSize,
fontFamily: this.selectedFont,
fontStyle: this.selectedFontStyle,
fill: this.selectedColor,
align: "center",
width: this.width * 0.5,
draggable: true
});
const transformer = new Konva.Transformer({
node: text,
keepRatio: true,
enabledAnchors: ["top-left", "top-right", "bottom-left", "bottom-right"]
});
text.on("click", async () => {
for (let i = 0; i < this.texts.length; i++) {
let item = this.texts[i];
if (item.index === text.index) {
let transformer = item.transformer;
this.selectedNode = { type: "text", text, transformer };
this.selectedFontSize = text.fontSize();
this.selectedFont = text.fontFamily();
this.selectedFontStyle = text.fontStyle();
this.selectedColor = text.fill();
vm.hideAllHelpers();
transformer.show();
transformer.moveToTop();
text.moveToTop();
vm.layer.draw();
break;
}
}
});
text.on("mouseover", () => {
transformer.show();
this.layer.draw();
});
text.on("mouseout", () => {
if (
(this.selectedNode &&
this.selectedNode.text &&
this.selectedNode.text.index != text.index) ||
(this.selectedNode && this.selectedNode.type === "image") ||
!this.selectedNode
) {
transformer.hide();
this.layer.draw();
}
});
text.on("dblclick", () => {
text.hide();
transformer.hide();
vm.layer.draw();
let textPosition = text.absolutePosition();
let stageBox = vm.stage.container().getBoundingClientRect();
let areaPosition = {
x: stageBox.left + textPosition.x,
y: stageBox.top + textPosition.y
};
let textarea = document.createElement("textarea");
window.document.body.appendChild(textarea);
textarea.value = text.text();
textarea.style.position = "absolute";
textarea.style.top = areaPosition.y + "px";
textarea.style.left = areaPosition.x + "px";
textarea.style.width = text.width() - text.padding() * 2 + "px";
textarea.style.height = text.height() - text.padding() * 2 + 5 + "px";
textarea.style.fontSize = text.fontSize() + "px";
textarea.style.border = "none";
textarea.style.padding = "0px";
textarea.style.margin = "0px";
textarea.style.overflow = "hidden";
textarea.style.background = "none";
textarea.style.outline = "none";
textarea.style.resize = "none";
textarea.style.lineHeight = text.lineHeight();
textarea.style.fontFamily = text.fontFamily();
textarea.style.transformOrigin = "left top";
textarea.style.textAlign = text.align();
textarea.style.color = text.fill();
let rotation = text.rotation();
let transform = "";
if (rotation) {
transform += "rotateZ(" + rotation + "deg)";
}
let px = 0;
let isFirefox =
navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isFirefox) {
px += 2 + Math.round(text.fontSize() / 20);
}
transform += "translateY(-" + px + "px)";
textarea.style.transform = transform;
textarea.style.height = "auto";
textarea.focus();
// start
function removeTextarea() {
textarea.parentNode.removeChild(textarea);
window.removeEventListener("click", handleOutsideClick);
text.show();
transformer.show();
transformer.forceUpdate();
vm.layer.draw();
}
function setTextareaWidth(newWidth) {
if (!newWidth) {
// set width for placeholder
newWidth = text.placeholder.length * text.fontSize();
}
// some extra fixes on different browsers
let isSafari = /^((?!chrome|android).)*safari/i.test(
navigator.userAgent
);
let isFirefox =
navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isSafari || isFirefox) {
newWidth = Math.ceil(newWidth);
}
let isEdge =
document.documentMode || /Edge/.test(navigator.userAgent);
if (isEdge) {
newWidth += 1;
}
textarea.style.width = newWidth + "px";
}
textarea.addEventListener("keydown", function(e) {
// hide on enter
// but don't hide on shift + enter
if (e.keyCode === 13 && !e.shiftKey) {
text.text(textarea.value);
removeTextarea();
}
// on esc do not set value back to node
if (e.keyCode === 27) {
removeTextarea();
}
});
textarea.addEventListener("keydown", function(e) {
let scale = text.getAbsoluteScale().x;
setTextareaWidth(text.width() * scale);
textarea.style.height = "auto";
textarea.style.height =
textarea.scrollHeight + text.fontSize() + "px";
});
function handleOutsideClick(e) {
if (e.target !== textarea) {
text.text(textarea.value);
removeTextarea();
}
}
setTimeout(() => {
window.addEventListener("click", handleOutsideClick);
});
// end
});
text.transformer = transformer;
this.texts.push(text);
this.layer.add(text);
this.layer.add(transformer);
this.hideAllHelpers();
this.selectedNode = { type: "text", text, transformer };
transformer.show();
this.layer.draw();
},
initCanvas() {
const vm = this;
this.stage = new Konva.Stage({
container: "container",
width: vm.width,
height: vm.height
});
this.layer = new Konva.Layer();
this.stage.add(this.layer);
let video = document.createElement("video");
video.setAttribute("id", "video");
video.setAttribute("ref", "video");
if (this.source) {
video.src = this.source;
}
video.preload = "auto";
video.loop = "loop";
video.style.display = "none";
this.video = video;
this.backgroundVideo = new Konva.Image({
image: vm.video,
draggable: false
});
this.video.addEventListener("loadedmetadata", function(e) {
vm.backgroundVideo.width(vm.width);
vm.backgroundVideo.height(vm.height);
});
this.video.addEventListener("ended", () => {
console.log("the video ended");
this.anim.stop();
this.anim.start();
this.video.loop = "loop";
this.video.play();
});
this.anim = new Konva.Animation(function() {
console.log("animation called");
// do nothing, animation just need to update the layer
}, vm.layer);
this.layer.add(this.backgroundVideo);
this.layer.draw();
const canvas = document.getElementsByTagName("canvas")[0];
canvas.style.border = "3px solid red";
this.canvas = canvas;
}
}
};
</script>
<style scoped>
body {
margin: 0;
padding: 0;
background-color: #f0f0f0;
}
.backgrounds,
.images {
width: 100px;
height: 100px;
padding-left: 2px;
padding-right: 2px;
}
</style>
You must use keyframes in order to get the animation to render properly once you do that you will have no issues.
Related
I am trying to connect a Kurento Media Server to React Native on iOS for a group video call.
The server is running on Nodejs and Docker, and they already have a TURN server set up.
The app is already running in the web browser via kurento-utils, but I can't stream the web video to RN, instead, the RN video looks just fine on the web.
I'm not usig kurento-utils on client side of React Native.
This is my code:
Video Screen (emmiter and receptor)
import React, { useState, useEffect } from 'react';
import {
View,
StyleSheet,
Text,
} from 'react-native';
import Display from 'react-native-display';
import InCallManager from 'react-native-incall-manager';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { socket } from '../../screens/User';
import ReceiveScreen from './ReceiveScreen';
import {
RTCView,
RTCIceCandidate,
} from 'react-native-webrtc';
import {
startCommunication,
receiveVideo,
addIceCandidate,
ProcessAnswer,
ViewPCArray
} from '../../utils/webrtc-utils';
const participants = {};
function sendMessage(message) {
if (socket) {
socket.emit('message', message);
}
}
const VideoScreen = () => {
const [videoURL, setVideoURL] = useState(null)
const [remoteURL, setRemoteURL] = useState(null)
const [userName, setUserName] = useState(socket.id)
const [roomName, setRoomName] = useState('9b33737f-737f-4a3d-a323-e1cd3f4b68b2')
useEffect(() => {
if(socket) {
var message = {
event: 'joinRoom',
userName: userName,
roomName: roomName
}
sendMessage(message);
}
socket.on('message', message => {
messageProcessHandler(message);
});
return () => {
socket.close();
}
}, [])
/**
*
* #param {*} msg
*/
const messageProcessHandler = (msg) => {
// console.log(`MessageProcessHandler: ${msg.event}`);
switch (msg.event) {
case 'existingParticipants':
console.log('LOG:176 onExistingParticipants= ' + JSON.stringify(msg));
startCommunication(sendMessage, userName, (stream) => {
setVideoURL(stream.toURL())
msg.existingUsers.forEach((object) => {
participants[object.name] = object.id;
console.log("participants:" + JSON.stringify(participants));
receiveVideo(sendMessage, object.name, (pc) => {
console.log('getRemoteStreams ', pc);
pc.getRemoteStreams().forEach(track => {
// console.log('TRACK: ', track);
console.log('STREAM', track);
// pc.addTrack(track, stream);
setRemoteURL(track.toURL())
})
});
});
});
break;
case 'newParticipantArrived':
participants[msg.name] = msg.name;
if (remoteURL == null || remoteURL === '') {
receiveVideo(sendMessage, msg.userName, (pc) => {
pc.getRemoteStreams().forEach(track => {
console.log('STREAM', track);
setRemoteURL(track.toURL())
})
});
}
break;
case 'participantLeft':
participantLeft(msg.name);
break;
case 'receiveVideoAnswer':
ProcessAnswer(msg.senderid, msg.sdpAnswer, (err) => {
if (err) {
console.log('the error: ' + err);
}
});
break;
case 'candidate':
addIceCandidate(msg.userid, new RTCIceCandidate(msg.candidate));
break;
default:
console.error('Unrecognized message', msg.message);
}
}
/**
*
* #param {*} name
*/
const participantLeft = (name) => {
if (participants[name]) {
delete participants[name];
}
if (Object.keys(participants).length == 0) {
setRemoteURL(null)
}
}
return (
<View style={styles.container}>
<View style={{position: "absolute", top: 40, right: 20, zIndex: 200}}>
<Text style={{fontSize: 20, fontWeight: "bold", color: "#fff"}}>{userName}</Text>
</View>
<RTCView zOrder={0} objectFit='cover' style={styles.videoContainer} streamURL={videoURL} />
<Display enable={remoteURL != null}>
<View style={styles.floatView}>
<ReceiveScreen videoURL={remoteURL} />
</View>
</Display>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1
},
videoContainer: {
flex: 1
},
floatView: {
position: 'absolute',
width: 250,
height: 210,
bottom: 15,
right: 20,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
borderRadius: 15
}
});
export default VideoScreen
Display (where the browser video is supposed to be)
import React, { Component } from 'react';
import config from "../../config/app.js";
import {
View,
Text,
ScrollView,
StyleSheet
} from 'react-native';
import { RTCView } from 'react-native-webrtc';
const styles = StyleSheet.create({
root: {
flex: 1
},
titleContainer: {
flex: 0.1,
paddingVertical: 2,
paddingLeft: 10
},
title: {
fontSize: 15,
color: 'rgba(255, 255, 255, 0.5)'
},
contentContainer: {
flex: 1,
backgroundColor: 'transparent'
}
});
const ReceiveScreen = (props) => {
return (
<View style={styles.root}>
<View style={styles.titleContainer}>
<Text style={styles.title}>Nuevo miembro</Text>
</View>
<RTCView objectFit='cover' zOrder={1} style={styles.contentContainer} streamURL={props.videoURL} mirror={true} />
</View>
)
}
export default ReceiveScreen;
WebRtc Utils
import {
getUserMedia,
MediaStreamTrack,
mediaDevices,
RTCPeerConnection,
RTCSessionDescription,
} from 'react-native-webrtc';
import { PERMISSIONS, request, checkMultiple } from 'react-native-permissions';
import { socket } from '../screens/User';
let pcArray = {};
let isFront = true;
let isEnableAudio = true;
let isEnableVideo = true;
let localstream = null;
const ICE_CONFIG = {
'iceServers': [
{
'urls': 'turn:xxx.xxx.xxx.xxx:3478',
'username': 'xxxxxxxx',
'credential': 'xxxxxxxxxxxxxxxxxxxxxxx',
'credentialType': 'password'
}
]
};
/**
* Obtenga elementos multimedia locales (transmisiones de video)
*
* #param {*} _sendMessage
* #param {*} _name
* #param {*} callback
*/
export function startCommunication(_sendMessage, _name, callback) {
getStream(true, stream => {
localstream = stream;
let options = {
audio: true,
video : {
mandatory : {
maxWidth : 320,
maxFrameRate : 15,
minFrameRate : 1
}
}
};
let pc = createPC(_sendMessage, _name, true, options);
pcArray[_name] = pc;
callback(stream);
});
}
/**
* Obtener transmisión de video remota
*
* #param {*} _sendMessae
* #param {*} _name
* #param {*} callback
*/
export function receiveVideo(_sendMessae, _name, callback) {
console.log("receiveVideo:", _sendMessae, _name, callback);
let options = {
audio: true,
video : {
mandatory : {
// maxWidth : 320,
// maxFrameRate : 15,
// minFrameRate : 15
maxWidth : 120,
maxHeight: 80,
minWidth : 80,
minHeight: 60,
maxFrameRate : 10,
minFrameRate : 10
}
}
};
let pc = createPC(_sendMessae, _name, true, options);
// console.log(`PC CREATED FOR ${_name}: ${JSON.stringify(pc)}`);
pcArray[_name] = pc;
// callback(pc);
callback(pcArray);
}
/**
* Encender/apagar el micrófono
*/
export function toggleAudio() {
if (localstream) {
isEnableAudio = !isEnableAudio;
localstream.getAudioTracks().forEach((track) => {
track.enabled = isEnableAudio;
});
} else {
console.log('in toggleAudio(), localstream is empty');
}
return isEnableAudio;
}
/**
* Activar/desactivar vídeo
*/
export function toggleVideo() {
if (localstream) {
isEnableVideo = !isEnableVideo;
localstream.getVideoTracks().forEach((track) => {
track.enabled = isEnableVideo;
});
} else {
console.log('in toggleVideo(), localstream is empty');
}
return isEnableVideo;
}
/**
* cambiar de cámara
*
*/
export function switchVideoType() {
if (localstream) {
localstream.getVideoTracks().forEach(track => {
track._switchCamera();
});
} else {
console.log('error');
}
}
/**
* Crear una transmisión de video local
*
* #param {*} isFront
* #param {*} callback
*/
export function getStream(isFront, callback) {
mediaDevices.enumerateDevices().then(sourceInfos => {
//console.log('Log: '+ JSON.stringify(sourceInfos)); // -> Es un objecto de 3
let videoSourceId;
for (let i = 0; i < sourceInfos.length; i++) {
const sourceInfo = sourceInfos[i];
if (sourceInfo.kind === 'videoinput' && sourceInfo.facing === (isFront ? 'front' : 'back')) {
// if (sourceInfo.kind === 'videoinput' && sourceInfo.facing === (isFront ? 'front' : 'back')) { Was video only, not videoinput
videoSourceId = sourceInfo.id;
}
}
request(PERMISSIONS.IOS.CAMERA).then((response) => {
if (response === 'granted') {
request(PERMISSIONS.IOS.CAMERA).then((response) => {
if (response === 'granted') {
mediaDevices.getUserMedia({
audio: true,
video: {
width: 640,
height: 480,
frameRate: 30,
facingMode: (isFront ? "user" : "environment"),
deviceId: videoSourceId
}
})
.then(stream => {
callback(stream)
})
.catch(error => {
// Log error
console.log(error)
});
}
})
}
})
});
}
/**
*
* Crear una conexión WebRTC
*
* #param {*} sendMessage
* #param {*} name
* #param {*} isOffer
* #param {*} options
*/
export function createPC(sendMessage, name, isOffer, options) {
let pc = new RTCPeerConnection(ICE_CONFIG);
pc.onnegotiationneeded = () => {
console.log('onnegotiationneeded');
if (isOffer) {
isOffer = false;
createOffer();
}
};
pc.onicecandidate = (event) => {
console.log('onicecandidate');
if (event.candidate) {
let msg = {
event: 'candidate',
// event: 'onIceCandidate',
userid: socket.id,
roomName: '9b33737f-737f-4a3d-a323-e1cd3f4b68b2',
candidate: event.candidate
};
sendMessage(msg);
}
};
pc.oniceconnectionstatechange = (event) => {
// console.log('oniceconnectionstatechange');
if (event.target.iceConnectionState === 'disconnected') {
//localstream.release();
// localstream = null;
if (pc !== null) {
pc.close();
pc = null;
}
}
};
pc.onsignalingstatechange = (event) => {
console.log('onsignalingstatechange: ', event.target.signalingState);
};
// send local stream
pc.addStream(localstream);
function createOffer() {
// console.log('createOffer');
pc.createOffer()
.then(function (desc) {
// console.log('...createOffer...');
pc.setLocalDescription(desc);
// something to do
let msg = {
event: 'receiveVideoFrom',
userid: socket.id,
roomName: '9b33737f-737f-4a3d-a323-e1cd3f4b68b2',
// sdpOffer: desc.sdp,
sdpOffer: desc.sdp,
};
sendMessage(msg);
})
.catch(err => console.error(err));
}
return pc;
}
/**
* Adición incremental de iceCandidate
*
* #param {*} name
* #param {*} candidate
*/
export function addIceCandidate(name, candidate) {
let pc = pcArray[name];
if (pc) {
pc.addIceCandidate(candidate);
} else {
console.log('pc.addIceCandidate failed : pc not exists');
}
}
/**
* Proceso SdpAnswer
*
* #param {*} name
* #param {*} sdp
* #param {*} callback
*/
export async function ProcessAnswer(name, sdp, callback) {
let pc = pcArray[name];
if (pc) {
let answer = {
type: 'answer',
sdp: sdp,
};
if (pc) {
// const remoteDesc = new RTCSessionDescription(answer);
// await pc.setRemoteDescription(remoteDesc);
await pc.setRemoteDescription(new RTCSessionDescription(answer)).then(function () {
console.log('LOG:252 Answer = ' + answer);
callback();
})
.catch(function (reason) {
callback(reason);
console.log('ERROR REASON:', reason);
});
}
} else {
console.log('ProcessAnswer failed : pc not exists');
}
}
/**
*
* Cierra la conexión y libera el streamer local.
*
*/
export function ViewPCArray (username) {
// console.log(pcArray[username].getRemoteStreams()[0]);
return pcArray
}
export function ReleaseMeidaSource() {
if (localstream) {
localstream.release();
localstream = null;
}
if (pcArray !== null) {
for (let mem in pcArray) {
pcArray[mem].close();
delete pcArray[mem];
}
}
}
function logError(error) {
console.log('logError', error);
}
NodeJs Kurento
module.exports = function(io, socket) {
// variables
const kurento = require('kurento-client');
const minimist = require('minimist');
var kurentoClient = null;
var iceCandidateQueues = {};
// constants
var argv = minimist(process.argv.slice(2), {
default: {
// as_uri: 'https://localhost:3000/',
// ws_uri: 'ws://localhost:8888/kurento'
as_uri: 'https://localhost:4000/',
ws_uri: 'ws://localhost:8888/kurento'
}
});
socket.on('message', function (message) {
console.log('Message received: ', message.event);
switch (message.event) {
case 'joinRoom':
joinRoom(socket, message.userName, message.roomName, err => {
if (err) {
console.log(err);
}
});
break;
case 'receiveVideoFrom':
receiveVideoFrom(socket, message.userid, message.roomName, message.sdpOffer, err => {
if (err) {
console.log(err);
}
});
break;
case 'candidate':
addIceCandidate(socket, message.userid, message.roomName, message.candidate, err => {
if (err) {
console.log(err);
}
});
break;
}
socket.on('disconnect', () => {
leaveRoom(socket, message.roomName, err => {
if (err) {
console.log(err);
};
});
})
});
// signaling functions
function joinRoom(socket, username, roomname, callback) {
console.log(('Smeone in the Room').green + socket + " " + username + " " + roomname)
getRoom(socket, roomname, (err, myRoom) => {
if (err) {
return callback(err);
}
myRoom.pipeline.create('WebRtcEndpoint', (err, outgoingMedia) => {
outgoingMedia.setMaxVideoSendBandwidth(100);
if (err) {
return callback(err);
}
var user = {
id: socket.id,
name: username,
outgoingMedia: outgoingMedia,
incomingMedia: {}
}
let iceCandidateQueue = iceCandidateQueues[user.id];
if (iceCandidateQueue) {
while (iceCandidateQueue.length) {
let ice = iceCandidateQueue.shift();
console.error(`user: ${user.name} collect candidate for outgoing media`);
user.outgoingMedia.addIceCandidate(ice.candidate);
}
}
user.outgoingMedia.on('OnIceCandidate', event => {
let candidate = kurento.register.complexTypes.IceCandidate(event.candidate);
socket.emit('message', {
event: 'candidate',
userid: user.id,
candidate: candidate
});
});
socket.to(roomname).emit('message', {
event: 'newParticipantArrived',
userid: user.id,
username: user.name
});
let existingUsers = [];
for (let i in myRoom.participants) {
if (myRoom.participants[i].id != user.id) {
existingUsers.push({
id: myRoom.participants[i].id,
name: myRoom.participants[i].name
});
}
}
socket.emit('message', {
event: 'existingParticipants',
existingUsers: existingUsers,
userid: user.id
});
myRoom.participants[user.id] = user;
});
});
}
// Disconnect from room
function leaveRoom(socket, roomname, callback) {
if (io.sockets.adapter.rooms[roomname] == null) {
// ROOMS
// console.log((io.sockets.adapter.rooms))
return;
}
var userSession = io.sockets.adapter.rooms[roomname].participants[socket.id];
var myRoom = io.sockets.adapter.rooms[roomname] || { length: 0 };
// MY ROOM
// console.log(myRoom);
if (!userSession) {
return;
}
var room = io.sockets.adapter.rooms[roomname];
if(!room){
return;
}
console.log('notify all user that ' + userSession.name + ' is leaving the room ' + roomname);
var usersInRoom = room.participants;
delete usersInRoom[userSession.id];
userSession.outgoingMedia.release();
// release incoming media for the leaving user
for (var i in userSession.incomingMedia) {
userSession.incomingMedia[i].release();
delete userSession.incomingMedia[i];
}
for (var i in usersInRoom) {
var user = usersInRoom[i];
// release viewer from this
user.incomingMedia[userSession.id].release();
delete user.incomingMedia[userSession.id];
// notify all user in the room
io.emit('message', {
event: 'participantLeft',
name: userSession.id
});
console.log('Mensaje emitido')
}
// Release pipeline and delete room when room is empty
if (Object.keys(room.participants).length == 0) {
room.pipeline.release();
delete rooms[userSession.roomName];
}
delete userSession.roomName;
// console.log(myRoom);
callback(null);
}
function receiveVideoFrom(socket, userid, roomname, sdpOffer, callback) {
getEndpointForUser(socket, roomname, userid, (err, endpoint) => {
if (err) {
return callback(err);
}
endpoint.processOffer(sdpOffer, (err, sdpAnswer) => {
if (err) {
return callback(err);
}
socket.emit('message', {
event: 'receiveVideoAnswer',
senderid: userid,
sdpAnswer: sdpAnswer
});
endpoint.gatherCandidates(err => {
if (err) {
return callback(err);
}
});
});
});
}
function addIceCandidate(socket, senderid, roomname, iceCandidate, callback) {
// console.log(io.sockets.adapter.rooms);
let user = io.sockets.adapter.rooms[roomname].participants[socket.id];
if (user != null) {
let candidate = kurento.register.complexTypes.IceCandidate(iceCandidate);
if (senderid == user.id) {
if (user.outgoingMedia) {
user.outgoingMedia.addIceCandidate(candidate);
} else {
iceCandidateQueues[user.id].push({ candidate: candidate });
}
} else {
if (user.incomingMedia[senderid]) {
user.incomingMedia[senderid].addIceCandidate(candidate);
} else {
if (!iceCandidateQueues[senderid]) {
iceCandidateQueues[senderid] = [];
}
iceCandidateQueues[senderid].push({ candidate: candidate });
}
}
callback(null);
} else {
callback(new Error("addIceCandidate failed"));
}
}
// useful functions
function getRoom(socket, roomname, callback) {
var myRoom = io.sockets.adapter.rooms[roomname] || { length: 0 };
var numClients = myRoom.length;
console.log(roomname, ' has ', numClients, ' clients');
if (numClients == 0) {
socket.join(roomname, () => {
myRoom = io.sockets.adapter.rooms[roomname];
getKurentoClient((error, kurento) => {
kurento.create('MediaPipeline', (err, pipeline) => {
if (error) {
return callback(err);
}
myRoom.pipeline = pipeline;
myRoom.participants = {};
callback(null, myRoom);
});
});
});
} else {
socket.join(roomname);
callback(null, myRoom);
}
}
function getEndpointForUser(socket, roomname, senderid, callback) {
var myRoom = io.sockets.adapter.rooms[roomname];
var asker = myRoom.participants[socket.id];
var sender = myRoom.participants[senderid];
if (asker.id === sender.id) {
return callback(null, asker.outgoingMedia);
}
if (asker.incomingMedia[sender.id]) {
sender.outgoingMedia.connect(asker.incomingMedia[sender.id], err => {
if (err) {
return callback(err);
}
callback(null, asker.incomingMedia[sender.id]);
});
} else {
myRoom.pipeline.create('WebRtcEndpoint', (err, incoming) => {
incoming.setMaxVideoSendBandwidth(100);
if (err) {
return callback(err);
}
asker.incomingMedia[sender.id] = incoming;
let iceCandidateQueue = iceCandidateQueues[sender.id];
if (iceCandidateQueue) {
while (iceCandidateQueue.length) {
let ice = iceCandidateQueue.shift();
console.error(`user: ${sender.name} collect candidate for outgoing media`);
incoming.addIceCandidate(ice.candidate);
}
}
incoming.on('OnIceCandidate', event => {
let candidate = kurento.register.complexTypes.IceCandidate(event.candidate);
console.log("CANDIDATE: ", event.candidate);
socket.emit('message', {
event: 'candidate',
userid: sender.id,
candidate: candidate
});
});
sender.outgoingMedia.connect(incoming, err => {
if (err) {
return callback(err);
}
callback(null, incoming);
});
});
}
}
function getKurentoClient(callback) {
if (kurentoClient !== null) {
return callback(null, kurentoClient);
}
kurento(argv.ws_uri, function (error, _kurentoClient) {
if (error) {
console.log("Could not find media server at address " + argv.ws_uri);
return callback("Could not find media server at address" + argv.ws_uri
+ ". Exiting with error " + error);
}
kurentoClient = _kurentoClient;
callback(null, kurentoClient);
});
}
}
I have a code in Vuejs that it uploads a image but I need to check the width and height of that image I have my code like this:
This is my input :
<input ref="file" accept="image/png" type="file" class="form-control" v-on:change="onFileChange" required>
This is a method that I use to upload when select the image :
onFileChange(e){
this.file = e.target.files[0];
this.noFile = e.target.files.length;
},
This is my code to upload :
onSubmit(e) {
this.loading = true; //the loading begin
e.preventDefault();
let currentObj = this;
const config = {
headers: { 'content-type': 'multipart/form-data' }
}
axios.post('/api/section/store?api_token='+App.apiToken, formData, config)
.then(function (response) {
currentObj.success = response.data.success;
})
.catch(function (error) {
console.log(error);
})
formData.append('file', this.file);
}
So I wonder how can I retrieve the width and height? I have tried this:
onFileChange(e){
this.file = e.target.files[0];
console.log(this.file.width);
this.noFile = e.target.files.length;
},
It says undefined, so how can I do that ?
A File object doesn't have a width and height. You would need to create an image object.
async onFileChange(e) {
this.file = e.target.files[0];
this.noFile = e.target.files.length;
const photoUrl = URL.createObjectURL(this.file);
const image = new Image();
const imageDimensions = await new Promise((resolve) => {
image.onload = () => {
const dimensions = {
height: image.height,
width: image.width,
};
resolve(dimensions);
};
image.src = photoUrl;
});
console.log(imageDimensions);
}
I have been studying some basics of Three.js into
a Vue.JS application.
I am following a tutorial and the code is the same as the original autor.
A cube geometry was added as a initial test, but my screen canvas goes black, and since i'm new to vuejs I can't find the error.
See some of the code below..
thank you all!
<template>
<div id="space">
<div id="container"></div>
</div>
</template>
<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
export default {
name: "space",
data() {
return {
scene: null,
camera: null,
renderer: null,
controls: null,
iR: null,
};
},
mounted() {
let that = this;
this.Awake();
window.addEventListener("resize", onWindowResize, false);
function onWindowResize() {
that.camera.aspect = window.innerWidth / window.innerHeight;
that.camera.updateProjectionMatrix();
that.renderer.setSize(window.innerWidth, window.innerHeight);
}
onWindowResize();
},
methods: {
Awake() {
let container = document.getElementById("container");
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x222222);
this.camera = new THREE.PerspectiveCamera(
25,
window.clientWidth / window.clientHeight,
1,
100
);
this.camera.position.set(8, 4, 0);
this.iR = new THREE.Group();
this.iR = "Interactive Root";
this.scene.add(this.iR);
let light0 = new THREE.AmbientLight(0xfafafa, 0.25);
let light1 = new THREE.PointLight(0xfafafa, 0.4);
light1.position.set(200, 90, 40);
let light2 = new THREE.PointLight(0xfafafa, 0.4);
light2.position.set(200, 90, -40);
this.scene.add(light0);
this.scene.add(light1);
this.scene.add(light2);
let gridHelper = new THREE.GridHelper(
60,
150,
new THREE.Color(0x555555),
new THREE.Color(0x333333)
);
this.scene.add(gridHelper);
//mesh geometry to test
let geometry = new THREE.BoxGeometry(1, 1, 1);
let material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
let mesh = new THREE.Mesh(geometry, material);
this.scene.add(mesh);
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(this.renderer.domElement);
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.25;
this.controls.screenSpacePanning = false;
this.controls.maxDistance = 800;
this.controls.update();
this.Update();
this.GetGeoJson();
},
Update() {
requestAnimationFrame(this.Update);
this.renderer.render(this.scene, this.camera);
this.controls.update();
},
GetGeoJson() {
fetch("./assets/ufpe.geojson").then((res) => {
res.json().then((data) => {
console.log(data);
//this.LoadBuildings(data)
});
});
},
},
};
</script>
<style scoped>
</style>
I am having a video site. Where users can upload YouTube videos. The teasers of the videos should be playing on top of the same thumbnail. However they are playing on the thumbnail to the right behind the next thumbnail. and the last one is playing outside the container. Below is my code
kYoutube.vue
<script>
const getLogger = require('webpack-log');
const log = getLogger({ name: 'vue-khal' });
import VueYoutube from 'vue-youtube';
export default {
extends: VueYoutube,
methods : {
playerStateChange(e) {
log.info ("PSC on " + e.data);
if (e.data !== null && e.data !== -1) {
this.$emit(this.events[e.data], e.target)
}
}
}
}
</script>
PreVideo.vue
<template>
<div class="vid-cont">
<div class="thumb-place">
<PreImage :post="post" :sizes="sizes" #loaded="handleLoad"></PreImage>
</div>
<label v-if="post.proof_img == null">
<div #mouseover="startVideo" #click="emitClick" v-bind:class="thumbZ">
<img ref="pimage" :src="post.post_image.image"
:srcset="post.post_image.thumbs"
:sizes="sizes" style="object-fit: cover;"/>
<img v-if="playImg" class="pre-play-button" :src="playImg" :key="playImg"/>
</div>
<div v-if="iReady" class="player-cont" :key="iReady">
<youtube :video-id="videoId" ref="youtube"
#playing="playing" #ended="ended" #cued="ended" #paused="paused"
:width="width" :height="height"
:player-vars="playerVars" :fitParent="true"></youtube>
</div>
<div class="player-cont player-trans" #mouseout="pausePreview" #click="emitClick">
</div>
</label>
</div>
</template>
<script>
import PreImage from './PreImage';
const getLogger = require('webpack-log');
const log = getLogger({ name: 'vue-khal' });
export default {
name: 'Video',
props: [
'post',
'sizes',
],
components: {
PreImage
//VueYoutube
},
data() {
return {
'first': this.post.post_image,
'videoId': this.post.video_id,
'width': 0,
'height': 0,
'iReady': false,
'playerVars': {
'autoplay': 0,
'rel': 0,
'enablejsapi': 1,
'controls': 0,
'disablekb': 1,
'fs': 0,
'modestbranding': 1,
'cc_load_policy': 0,
'origin': document.location.origin
},
'playImg': null,
'pReload': false,
'rates': [],
'preview': false,
'duration': 0,
'count': 1,
'userState': -1,
'numSkips': 3,
};
},
computed: {
thumbZ: function() {
return {
'thumb-cont': true,
'thumb-top': (this.userState < 3)
};
},
},
mounted() {
var that = this;
this.api.map_prom.then(() => {
that.playImg = that.api.mkstatic("youtube_red.png");
that.pReload = !that.pReload;
});
},
updated() {
var that = this;
this.$nextTick(function() {
if (that.iReady && that.userState < 0) {
that.$refs.youtube.player.mute().then(() => {
that.toState(0);
that.$refs.youtube.player.playVideo().then(() => {
});
});
}
});
},
methods: {
emitClick() {
this.$emit("clicked");
},
pausePreview() {
this.toState(1);
this.$refs.youtube.player.stopVideo();
},
delayedPause() {
var that = this;
this.toState(2);
that.$refs.youtube.player.pauseVideo().then(function(){
//log.info ("Paused ?? ");
that.$refs.youtube.player.getPlayerState();/*.then(data => {
//log.info ("STATE is " + data);
});*/
});
},
jump() {
if (this.userState <= 2)
return;
var that = this;
var ct = parseInt(this.duration * this.count / this.numSkips);
//log.info ("Jump call " + that.count);
if (this.count <= this.numSkips) {
this.$refs.youtube.player.seekTo(ct, true).then(function(){
//that.$refs.youtube.player.getPlayerState().then(data => {
//log.info ("STATE " + data + " Jumped to " + ct + " at " + (new Date()).getTime());
that.count++;
that.$refs.youtube.player.pauseVideo().then(function(){
/*that.$refs.youtube.player.playVideo().then(function(){
//setTimeout(that.jump.bind(that), 10000);
});*/
});
//});
});
}
else {
this.$refs.youtube.player.stopVideo();
}
},
toState(state) {
//log.info ("From " + this.userState + " to " + state);
this.userState = state;
},
/* If you are paused, play */
paused() {
switch (this.userState) {
case 2:
//log.info ("Paused, playback " + this.count);
if (this.count <= this.numSkips) {
this.toState(3);
this.$refs.youtube.player.playVideo();
}
else {
this.$refs.youtube.player.stopVideo();
}
break;
case 3:
case 4:
var that = this;
that.$refs.youtube.player.playVideo().then(function(){
setTimeout(that.jump.bind(that), 2000);
});
break;
}
},
/* If you're playing, pause in a sec */
playing() {
//log.info ("Playing called " + this.userState);
switch (this.userState) {
case 0:
var that = this;
that.$refs.youtube.player.pauseVideo().then(() => {
if (!that.duration) {
that.$refs.youtube.player.mute().then(() => {
that.$refs.youtube.player.getDuration().then(data => {
that.duration = data;
//log.info ("Duration " + that.duration);
that.$refs.youtube.player.getAvailablePlaybackRates().then (data => {
//log.info ("Data is " + data);
that.rates = data;
let rate = Math.max.apply(null, that.rates);
that.$refs.youtube.player.setPlaybackRate(rate).then(() => {
//log.info ("Suggested is " + rate);
this.toState(1);
});
});
});
});
}
else {
this.toState(1);
}
})
break;
case 3:
this.toState(4);
setTimeout(this.jump.bind(that), 2000);
}
},
startVideo() {
if (this.userState == 1) {
this.toState(2);
this.paused();
}
},
handleLoad(ev, post) {
this.post = post;
if (!this.$parent.modalOpen) {
//this.post.post_image = post_image;
this.height = ev.target.clientHeight;
this.width = ev.target.clientWidth;
log.info ("PREvid handle load** " + this.width);
this.iReady = true;
}
},
ended() {
//log.info ("ENDED indeed");
this.count = 1;
this.toState(0);
this.$refs.youtube.player.playVideo();
},
}
}
</script>
<style>
</style>
PostList.vue
<template>
<div>
<div ref="me" :class="postListClass" :style="`top: -${scrollOff}px`">
<Loading :loading="loading == 'L'" :loaderror="loading =='E'"></Loading>
<table class="table">
<tbody>
<tr v-for="row in posts" :key="row">
<td v-for="post in row" :key="post">
<div class="post-grid" v-if="post.post_image != null" ref="postgrid" :key="post">
<PreVideo v-if="post.video_id" :post="post" :sizes="sizes" #clicked="selectPost(post.pk)">
</PreVideo>
<PreVideoFile v-else-if="post.video_file" :post="post" :sizes="sizes" #clicked="selectPost(post.pk)">
</PreVideoFile>
<PreImage v-else :post="post" :sizes="sizes" #clicked="selectPost(post.pk)">
</PreImage>
<div class="post-title" #click="selectPost(post.pk)">
<KTruncate :inhtml="post.title" :stripped="post.title" :nomore="true" length="30"></KTruncate>
</div>
<div class="post-user">
by <a :href="post.user.profile.url">{{post.user.full_name}}</a>
</div>
<div class="post-synopsis">
<KTruncate :inhtml="post.message" :stripped="post.stripped" :nomore="true" length="60">
</KTruncate>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<div class="way-container" v-if="nextPageURL !== '' || loading == 'E'">
<Loading :loading="'L'" :key="loading" :loaderror="loading == 'E'" class="way-div" v-waypoint="{ active: true, callback: onWaypoint, options: intersectionOptions }" id="More">
</Loading>
</div>
</div>
<PostModal v-if="selectedPost > 0" ref="modal" :pk="selectedPost"
#closed="onHideDetail" #modalOut="onModalOut" :key="selectedPost"></PostModal>
</div>
</template>
<script>
/* eslint-disable no-console */
import Loading from './Loading';
import PostModal from './PostModal';
import KTruncate from './KTruncate';
import PreVideo from './PreVideo';
import PreVideoFile from './PreVideoFile';
import PreImage from './PreImage';
const getLogger = require('webpack-log');
const log = getLogger({ name: 'vue-khal' });
const getOffsetTop = element => {
let offsetTop = 0;
while(element) {
offsetTop += element.offsetTop;
element = element.offsetParent;
}
return offsetTop;
}
export default {
name: 'PostList',
components: {
Loading,
PostModal,
KTruncate,
PreVideo,
PreVideoFile,
PreImage
},
data() {
return {
scrollOff: 0,
origTop: null,
postListClass: "post-scroll",
selectedPost:0,
posts: [],
numberOfPages:0,
pages : [],
numberOfPosts:0,
loading: null, //false,
nextPageURL:'',
previousPageURL:'',
size:'',
winWidth: window.innerWidth,
intersectionOptions: {
root: null,
//rootMargin: '0px 0px 0px 0px',
threshold: [0.25, 0.75] // [0.25, 0.75] if you want a 25% offset!
},
divisor: 4,
modalOpen: false,
};
},
watch: {
pk: function (newpk, oldpk) {
log.info ("Change from " + oldpk + " to " + newpk);
}
},
methods: {
onWaypoint ({ going, direction }) {
if (!this.loading) {
// going: in, out
// direction: top, right, bottom, left
if (going === this.$waypointMap.GOING_IN &&
direction === this.$waypointMap.DIRECTION_TOP &&
this.nextPageURL != '') {
log.info ("Waypoint " + this.nextPageURL + " ??");
this.loading = 'L'; //true;
this.api.getItemsByURL(this.nextPageURL).then((page) => {
this.posts = this.posts.concat(this.breakupPosts(page.data));// this.posts = page.data;
this.nextPageURL = page.nextlink;
this.previousPageURL = page.prevlink;
this.loading = null; //false;
}).catch (err => {
log.info ("Waypoint err " + err.code + " " + err.message);
this.loading = 'E';
});
}
}
},
breakupPosts(data) {
let posts = [];
for (let i = 0; i < data.length; i++) {
let row = parseInt(i / this.divisor);
if (row >= posts.length)
posts.push([]);
posts[parseInt(i / this.divisor)].push(data[i]);
}
return posts;
/*this.posts = page.data.reduce((acc, k) => {
acc[parseInt(k/3)].push(page.data[k])
return acc;
}, {});*/
},
getPosts(){
if (this.origTop == null) {
this.origTop = getOffsetTop(this.$refs.me);
}
this.loading = 'L'; //true;
this.api.getFeedPosts().then((page) => {
this.setPage(page);
this.numberOfPosts = page.count;
this.numberOfPages = page.numpages;
if(this.numberOfPages) {
for(var i = 1 ; i <= this.numberOfPages ; i++) {
const link = `/api/posts/?page=${i}`;
this.pages.push({pageNumber: i , link: link})
}
}
}).catch (err => {
log.info ("Final status* " + err.code + " " + err.message);
this.loading = 'E';
});
},
getPage(link){
this.loading = 'L'; //true;
this.api.getItemsByURL(link).then((page) => {
this.setPage(page);
}).catch (err => {
log.info ("GetPage err " + err.code + " " + err.message);
this.loading = 'E';
});
},
getNextPage(){
this.loading = 'L'; // true;
this.api.getItemsByURL(this.nextPageURL).then((page) => {
this.setPage(page);
}).catch (err => {
log.info ("GetNextPage err " + err.code + " " + err.message);
this.loading = 'E';
});
},
setPage(page) {
this.posts = this.breakupPosts(page.data);//this.posts = page.data;
this.nextPageURL = page.nextlink;
this.previousPageURL = page.prevlink;
this.sizes = page.sizes;
this.loading = null; //false;
},
getPreviousPage(){
this.loading = 'L'; // true;
this.api.getItemsByURL(this.previousPageURL).then((page) => {
this.setPage(page);
}).catch (err => {
log.info ("Get Prev err " + err.code + " " + err.message);
this.loading = 'E';
});
},
onHideDetail() {
this.postListClass = "post-scroll";
let sides = document.getElementsByClassName ("tofix");
for (let i = 0; i < sides.length; i++) {
sides[i].classList.remove ("postbg-fixed");
}
log.info ("On hide detail " + this.selectedPost);
this.selectedPost = 0;
},
onModalOut() {
setTimeout(this.scroller.bind(this), 100);
},
scroller() {
document.documentElement.scrollTop = document.body.scrollTop = this.scrollOff + this.origTop; //document.body.scrollTop = this.scrollOff;
this.scrollOff = 0;
},
selectPost(post) {
log.info ("Select post called " + post );
this.selectedPost = post;
this.modalOpen = true;
this.scrollOff = (window.pageYOffset || document.documentElement.scrollTop) - this.origTop;
this.postListClass = "post-fixed";
let sides = document.querySelectorAll ("tofix");
for (let i = 0; i < sides.length; i++) {
sides[i].classList.add ("postbg-fixed");
}
log.info ("Select post done " + this.selectedPost);
},
getDivisor() {
let u600 = window.matchMedia('(min-width: 600px)');
let u900 = window.matchMedia('(min-width: 900px)');
this.divisor = u600.matches ? (u900.matches ? 4 : 3) : 2;
},
handleResize() {
if (window.innerWidth != this.winWidth) {
this.winWidth = window.innerWidth;
this.getDivisor();
this.posts = this.breakupPosts(this.posts.flat());
}
},
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
},
updated() {
},
mounted() {
this.getPosts();
this.getDivisor();
window.addEventListener('resize', this.handleResize);
},
}
</script>
<style scoped>
.table>tbody>tr>td {
border-top: transparent;
}
.post-fixed {
position: fixed;
z-index: -1;
opacity: 0;
}
#More {
-height: 100px;
}
</style>
how can i create d.3js Zoomable Circle Packing in vue js
like this
https://bl.ocks.org/fdlk/076469462d00ba39960f854df9acda56
I just created an example with the chart from the link. The main thing is that you have to call D3 code in the mounted() life-cycle hook of the Vue instance and you also need to have the SVG element in your <template>
Example with the same chart:
const jsonData = {"name":"variants","children":[{"name":"2","children":[{"name":"p23.3","children":[{"name":"IFT172","children":[{"name":"undefined","size":28},{"name":"aaaacxi7gjs3gascvqjaabaaaq","size":3},{"name":"aaaacxi7gjs3eascvqjaabaaci","size":6},{"name":"aaaacxi7gjs3gascvqjaabaabe","size":6},{"name":"aaaacxi7gjs3gascvqjaabaacm","size":1},{"name":"aaaacxi7gjs3eascvqjaabaaca","size":8},{"name":"aaaacxi7gjs3gascvqjaabaace","size":7},{"name":"aaaacxi7gjs3eascvqjaabaab4","size":5},{"name":"aaaacxi7gjs3gascvqjaabaaae","size":3},{"name":"aaaacxi7gjs3eascvqjaabaace","size":9},{"name":"aaaacxi7gjs3eascvqjaabaacq","size":9},{"name":"aaaacxi7gjs3gascvqjaabaaa4","size":4},{"name":"aaaacxi7gjs3eascvqjaabaaau","size":13},{"name":"aaaacxi7gjs3gascvqjaabaaay","size":5},{"name":"aaaacxi7gjs3eascvqjaabaaby","size":6},{"name":"aaaacxi7gjs3eascvqjaabaabq","size":5},{"name":"aaaacxi7gjs3gascvqjaabaaam","size":6},{"name":"aaaacxi7gjs3iascvqjaabaaaq","size":6},{"name":"aaaacxi7gjs3eascvqjaabaabi","size":5},{"name":"aaaacxi7gjs3iascvqjaabaaae","size":3},{"name":"aaaacxi7gjs3gascvqjaabaacu","size":2},{"name":"aaaacxi7gjs3eascvqjaabaabm","size":3},{"name":"aaaacxi7gjs3gascvqjaabaabu","size":3},{"name":"aaaacxi7gjs3iascvqjaabaaai","size":7},{"name":"aaaacxi7gjs3gascvqjaabaac4","size":3},{"name":"aaaacxi7gjs3gascvqjaabaabq","size":4},{"name":"aaaacxi7gjs3eascvqjaabaaay","size":6},{"name":"aaaacxi7gjs3gascvqjaabaabm","size":2},{"name":"aaaacxi7gjs3iascvqjaabaaa4","size":5},{"name":"aaaacxi7gjs3iascvqjaabaaau","size":7},{"name":"aaaacxi7gjs3gascvqjaabaabi","size":5},{"name":"aaaacxi7gjs3iascvqjaabaaba","size":12},{"name":"aaaacxi7gjs3iascvqjaabaaay","size":4},{"name":"aaaacxi7gjs3gascvqjaabaaca","size":6},{"name":"aaaacxi7gjs3gascvqjaabaacq","size":4},{"name":"aaaacxi7gjs3eascvqjaabaaa4","size":4},{"name":"aaaacxi7gjs3iascvqjaabaabi","size":5},{"name":"aaaacxi7gjs3iascvqjaabaabe","size":1},{"name":"aaaacxi7gjs3gascvqjaabaaci","size":4},{"name":"aaaacxi7gjs3gascvqjaabaaai","size":4},{"name":"aaaacxi7gjs3gascvqjaabaaba","size":2},{"name":"aaaacxi7gjs3gascvqjaabaada","size":1},{"name":"aaaacxi7gjs3eascvqjaabaabe","size":2},{"name":"aaaacxi7gjs3eascvqjaabaacm","size":5},{"name":"aaaacxi7gjs3gascvqjaabaaau","size":2},{"name":"aaaacxi7gjs3iascvqjaabaaam","size":1},{"name":"aaaacxi7gjs3eascvqjaabaaba","size":1},{"name":"aaaacxi7gjs3gascvqjaabaab4","size":3}]}]}]},{"name":"3","children":[{"name":"p21.31","children":[{"name":"CACNA2D2","children":[{"name":"aaaacxi7gjufoascvqjaabaafi","size":2},{"name":"undefined","size":1},{"name":"aaaacxi7gjufqascvqjaabaaae","size":2},{"name":"aaaacxi7gjufqascvqjaabaadu","size":1},{"name":"aaaacxi7gjufqascvqjaabaaee","size":2},{"name":"aaaacxi7gjufqascvqjaabaace","size":1},{"name":"aaaacxi7gjufqascvqjaabaaca","size":2},{"name":"aaaacxi7gjufqascvqjaabaad4","size":2}]},{"name":"LARS2","children":[{"name":"aaaacxi7gj6pmascvqjaabaag4","size":30},{"name":"aaaacxi7gj6pmascvqjaabaagy","size":9},{"name":"aaaacxi7gj6pmascvqjaabaaga","size":16},{"name":"aaaacxi7gj6pmascvqjaabaagq","size":8},{"name":"aaaacxi7gj6pmascvqjaabaahi","size":11},{"name":"aaaacxi7gj6pmascvqjaabaagm","size":14},{"name":"aaaacxi7gj6pmascvqjaabaage","size":1},{"name":"undefined","size":10},{"name":"aaaacxi7gj6pmascvqjaabaaia","size":12},{"name":"aaaacxi7gj6pmascvqjaabaahy","size":15},{"name":"aaaacxi7gj6pmascvqjaabaafq","size":8},{"name":"aaaacxi7gj6pmascvqjaabaaha","size":10},{"name":"aaaacxi7gj6pmascvqjaabaagu","size":12},{"name":"aaaacxi7gj6pmascvqjaabaaf4","size":10},{"name":"aaaacxi7gj6pmascvqjaabaahq","size":10}]}]}]},{"name":"7","children":[{"name":"q22.1","children":[{"name":"TFR2","children":[{"name":"aaaacxi7gjz4wascvqjaabaadq","size":2},{"name":"aaaacxi7gjz4yascvqjaabaaaq","size":1},{"name":"undefined","size":24},{"name":"aaaacxi7gjz4wascvqjaabaacq","size":4},{"name":"aaaacxi7gjz4yascvqjaabaaay","size":1},{"name":"aaaacxi7gjz4wascvqjaabaadm","size":1},{"name":"aaaacxi7gjz4wascvqjaabaacy","size":2},{"name":"aaaacxi7gjz4yascvqjaabaabi","size":2},{"name":"aaaacxi7gjz4yascvqjaabaaau","size":6}]}]}]},{"name":"9","children":[{"name":"q22.2","children":[{"name":"SECISBP2","children":[{"name":"undefined","size":30},{"name":"aaaacxi7gkabkascvqjaabaaea","size":9},{"name":"aaaacxi7gkabkascvqjaabaae4","size":6},{"name":"aaaacxi7gkabkascvqjaabaaf4","size":6},{"name":"aaaacxi7gkabkascvqjaabaaey","size":7},{"name":"aaaacxi7gkabkascvqjaabaafq","size":10}]}]}]},{"name":"12","children":[{"name":"q15","children":[{"name":"NUP107","children":[{"name":"undefined","size":16},{"name":"aaaacxi7gj74sascvqjaabaaby","size":10},{"name":"aaaacxi7gj74sascvqjaabaade","size":7},{"name":"aaaacxi7gj74sascvqjaabaadq","size":9},{"name":"aaaacxi7gj74sascvqjaabaab4","size":7},{"name":"aaaacxi7gj74sascvqjaabaacu","size":9},{"name":"aaaacxi7gj74sascvqjaabaaem","size":7},{"name":"aaaacxi7gj74sascvqjaabaaa4","size":3},{"name":"aaaacxi7gj74sascvqjaabaace","size":8},{"name":"aaaacxi7gj74sascvqjaabaaee","size":7},{"name":"aaaacxi7gj74sascvqjaabaady","size":3},{"name":"aaaacxi7gj74sascvqjaabaabq","size":6},{"name":"aaaacxi7gj74sascvqjaabaaca","size":8},{"name":"aaaacxi7gj74sascvqjaabaabi","size":10},{"name":"aaaacxi7gj74sascvqjaabaabm","size":5},{"name":"aaaacxi7gj74sascvqjaabaacy","size":4},{"name":"aaaacxi7gj74sascvqjaabaada","size":10},{"name":"aaaacxi7gj74sascvqjaabaac4","size":6},{"name":"aaaacxi7gj74sascvqjaabaabu","size":9},{"name":"aaaacxi7gj74sascvqjaabaacm","size":7},{"name":"aaaacxi7gj74sascvqjaabaacq","size":3},{"name":"aaaacxi7gj74sascvqjaabaadu","size":3},{"name":"aaaacxi7gj74sascvqjaabaad4","size":1},{"name":"aaaacxi7gj74sascvqjaabaadi","size":4}]}]}]},{"name":"17","children":[{"name":"p13.1","children":[{"name":"SCO1","children":[{"name":"aaaacxi7gjwlyascvqjaabaacu","size":25},{"name":"aaaacxi7gjwlyascvqjaabaace","size":39}]}]}]},{"name":"19","children":[{"name":"q13.2","children":[{"name":"SPINT2","children":[{"name":"aaaacxi7gjuvmascvqjaabaadm","size":1},{"name":"undefined","size":5},{"name":"aaaacxi7gjuvmascvqjaabaadi","size":3},{"name":"aaaacxi7gjuvmascvqjaabaade","size":6},{"name":"aaaacxi7gjuvmascvqjaabaac4","size":6},{"name":"aaaacxi7gjuvmascvqjaabaada","size":4},{"name":"aaaacxi7gjuvmascvqjaabaadu","size":2},{"name":"aaaacxi7gjuvmascvqjaabaadq","size":1}]}]},{"name":"p13.12","children":[{"name":"PRKACA","children":[{"name":"aaaacxi7gkb32ascvqjaabaagi","size":9}]}]}]},{"name":"22","children":[{"name":"q13.33","children":[{"name":"SCO2","children":[{"name":"undefined","size":32},{"name":"aaaacxi7gka5qascvqjaabaaai","size":35},{"name":"aaaacxi7gka5oascvqjaabaacq","size":7},{"name":"aaaacxi7gka5qascvqjaabaaay","size":21},{"name":"aaaacxi7gj4eqascvqjaabaab4","size":115},{"name":"aaaacxi7gka5oascvqjaabaacm","size":5}]}]}]}]}
// vue configuration
var Main = {
data () {
return {
}
},
mounted() {
this.renderZoomableCircles()
},
methods: {
renderZoomableCircles(){
var svg = d3.select("svg"),
margin = 20,
diameter = +svg.attr("width"),
g = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
var color = d3.scaleLinear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.pack()
.size([diameter - margin, diameter - margin])
.padding(2);
var root = d3.hierarchy(jsonData)
.sum(function(d) { return d.size; })
.sort(function(a, b) { return b.value - a.value; });
var focus = root,
nodes = pack(root).descendants(),
view;
var circle = g.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", function(d) { return d.children ? color(d.depth) : null; })
.on("click", function(d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); });
var text = g.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) { return d.parent === root ? 1 : 0; })
.style("display", function(d) { return d.parent === root ? "inline" : "none"; })
.text(function(d) { return d.data.name; });
var node = g.selectAll("circle,text");
svg
.style("background", color(-1))
.on("click", function() { zoom(root); });
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus; focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) { zoomTo(i(t)); };
});
transition.selectAll("text")
.filter(function(d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function(d) { return d.parent === focus ? 1 : 0; })
.on("start", function(d) { if (d.parent === focus) this.style.display = "inline"; })
.on("end", function(d) { if (d.parent !== focus) this.style.display = "none"; });
}
function zoomTo(v) {
var k = diameter / v[2]; view = v;
node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
circle.attr("r", function(d) { return d.r * k; });
}
}
},
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
<meta charset="utf-8">
<style>
.node {
cursor: pointer;
}
.node:hover {
stroke: #000;
stroke-width: 1.5px;
}
.node--leaf {
fill: white;
}
.label {
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
text-anchor: middle;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}
.label,
.node--root,
.node--leaf {
pointer-events: none;
}
</style>
<div id="app">
<svg width="960" height="960"></svg>
</div>
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
I hope this helps.