How to modify stream on ongoing connection to pause/resume stream transmission - socket.io

I recently started learning and building an app for my school for remote online classes with WebRTC and PeerJs on my school's server (VPS). So far I am able to set up 1 to 1 peer connection but having a hard time pausing and resuming stream transmission.
I am seeking some help on how to pause and resume video and audio stream for self and remote user, while on active connection. When I execute localStream.getVideoTracks()[0].enabled = false, it only disable video for me (not for a remote user).
As some have suggested replaceTrack API, but I am not unable to find tutorials about it as I am new to it.
My code (Thanks to Link) looks like below:
var url = new URL(window.location.href)
var disableStreamInBeginning = url.searchParams.get("disableStreamInBeginning")
var passwordProtectedRoom = url.searchParams.get("passwordProtectedRoom")
var muteAllInBeginning = url.searchParams.get("muteAllInBeginning")
var isVideoCall = url.searchParams.get("isVideoCall")
var singleOrConference = url.searchParams.get("singleOrConference")
const conferenceView = document.getElementById('conference')
const loader = document.getElementById('loader')
const localVideoView = document.getElementById('local-video')
const remoteVideoView = document.getElementById('remote-video')
const remoteVideoDiv = document.getElementById('remote-video-div')
if(typeof disableStreamInBeginning !== 'undefined' && disableStreamInBeginning == 'true'){
var disbaleSelfStream = true
} else {
var disbaleSelfStream = false
}
if(typeof passwordProtectedRoom !== 'undefined' && passwordProtectedRoom == 'true'){
var passwordProtected = true
} else {
var passwordProtected = false
}
if(typeof muteAllInBeginning !== 'undefined' && muteAllInBeginning == 'true'){
var muteAll = true
} else {
var muteAll = false
}
if(typeof isVideoCall !== 'undefined' && isVideoCall == 'true'){
var videoCall = true
} else {
var videoCall = false
}
if(typeof singleOrConference !== 'undefined' && singleOrConference == 'conference'){
var isConference = true
conferenceView.style.display = 'block'
} else {
var isConference = false
localVideoView.style.opacity = 0
remoteVideoView.style.opacity = 0
remoteVideoDiv.style.opacity = 0
}
var selectedCamera = 'user'
let localStream;
const socket = io('/');
localVideoView.muted = true;
const peers = {};
const peer = new Peer(undefined, {
host: '/',
port: '443',
path: '/myapp',
secure: true
})
// Handelling incoming call connection
peer.on("call", async (call) => {
let stream = null;
try {
stream = await navigator.mediaDevices.getUserMedia(
{
video: {
facingMode: selectedCamera
},
audio: true
});
call.answer(stream);
call.on("stream", (remoteVideoStream) => {
addVideoStream(remoteVideoView, remoteVideoStream);
});
} catch (err) {
console.log('peer.on("call": ' + err);
};
});
// On new user connected
socket.on("user-connected", async (userId) => {
connectDataToNewUser(userId);
try {
stream = await navigator.mediaDevices.getUserMedia(
{
audio: true,
video: true,
})
}
catch (err) {
console.log('socket.on("user-connected": ' + err);
};
connectMediaToNewUser(userId, stream);
});
// Show own Video on own device screen
(async () => {
try {
localStream= await navigator.mediaDevices.getUserMedia(
{
video: {
facingMode: selectedCamera
},
audio: true
});
addVideoStream(localVideoView, localStream);
} catch (err) {
console.log('(async () =>: ' + err);
}
})();
peer.on("open", (id) => {
socket.emit("join-room", ROOM_ID, id);
});
peer.on("error", (err) => {
console.log('peer.on("error": ' + err);
})
socket.on("user-disconnected", (userId) => {
if (peers[userId]) {
peers[userId].close();
}
});
// Set up event listener for an "another user" data connection established event
peer.on("connection", (conn) => {
conn.on("data", (data) => {
console.log('Received data ' + data);
});
// Set up event listener for connection conn established event
conn.on("open", () => {
conn.send('Hello!');
});
});
// Initiate a Data call (Messages) to user
const connectDataToNewUser = (userId) => {
let conn = peer.connect(userId);
conn.on("data", (data) => {
console.log('Received data: ' + data);
});
conn.on("open", () => {
conn.send('hi!');
});
};
// Initiate a Media call (Audio/Video) to user
const connectMediaToNewUser = (userId, stream) => {
const call = peer.call(userId, stream);
call.on("stream", (userVideoStream) => {
addVideoStream(remoteVideoView, userVideoStream);
});
call.on("close", () => {
remoteVideoView.remove();
});
call.on("error", (error) => {
console.log('connectMediaToNewUser' + error);
});
peers[userId] = call;
};
const addVideoStream = (video, stream) => {
video.srcObject = stream;
video.addEventListener("loadedmetadata", () => {
if(disbaleSelfStream){
systemStream.getVideoTracks()[0].enabled = false
systemStream.getAudioTracks()[0].enabled = false
} else {
loader.style.opacity = 0
video.style.opacity = 1
video.play()
remoteVideoDiv.style.opacity = 0
}
});
};
Server Side Code:
const express = require('express')
const app = express()
const httpPort = process.env.PORT || 80
const httpsPort = 443
const { ExpressPeerServer } = require('peer')
const path = require('path')
const http = require('http')
const https = require('https')
const fs = require('fs')
// Certificate & credentials
const privateKey = fs.readFileSync(path.join(__dirname, 'certs', 'key.pem'))
const certificate = fs.readFileSync(path.join(__dirname, 'certs', 'cert.pem'))
const credentials = {
key: privateKey,
cert: certificate
}
const httpsServer = https.createServer(credentials, app).listen(httpsPort, () => { console.log('Peer Server listening to port ' + httpsPort) })
const peerServer = ExpressPeerServer(httpsServer, {
debug: true,
path: '/myapp'
})
app.use(peerServer)
const io = require('socket.io')(httpsServer, {
forceNew: true,
transports: ["polling"],
})
const { v4: uuidV4 } = require('uuid')
app.set('view engine', 'ejs')
app.use(express.static('public'))
app.get('/', (req, res) => {
res.redirect(`/${uuidV4()}`)
})
app.get('/:room', (req, res) => {
res.render('room', { roomId: req.params.room })
})
io.on('connection', (socket) => {
socket.on('join-room', (roomId, userId) => {
socket.join(roomId)
socket.broadcast.to(roomId).emit('user-connected', userId)
socket.on('disconnect', () => {
socket.broadcast.to(roomId).emit('user-disconnected', userId)
})
socket.on('text-message', message => {
socket.broadcast.to(roomId).emit('text-message-received', message)
})
socket.on('system-stream-updated', remoteUserId => {
socket.broadcast.to(roomId).emit('new-remote-stream', remoteUserId)
})
})
})
And room.ejs (if needed)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script>
const ROOM_ID = "<%= roomId %>"
</script>
<script src="peer.min.js" defer></script>
<script src="/socket.io/socket.io.js" defer></script>
<script src="client.js" defer></script>
<title>Interface</title>
<style type="text/css">
html, body {
padding: 0;
margin: 0;
}
.container, .local-video {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
}
.remote-video-div {
position: absolute;
max-width: 30%;
width: 30%;
margin: 16px;
}
.remote-video {
max-width: 100%;
width: 100%;
margin-bottom: -5px;
}
.video-inset {
outline: unset;
visibility: hidden;
position: relative;
margin:0;
padding:0;
}
.background-black {
background-color: #000000 !important;
}
.display-none {
display: none;
}
.loader {
margin: 250px auto;
border: 7px solid #9e9c9c;
border-radius: 50%;
border-top: 7px solid #ffffff;
width: 40px;
height: 40px;
-webkit-animation: spin 2s linear infinite; /* Safari */
animation: spin 2s linear infinite;
/* Safari */
#-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
#keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<link rel="icon" type="image/ico" href="favicon.ico"/>
</head>
<body>
<div class="local-video-div background-black">
<video class="local-video" autoplay></video>
</div>
<div class="container background-black display-none" id="loader">
<div class="loader"></div>
</div>
<div class="remote-video-div background-black">
<video class="remote-video" autoplay onclick="remoteVideoClick()"></video>
</div>
<div class="container background-black display-none" id="conference">
</div>
</body>
</html>
Thanks in advance.

I ran into the same problem and was looking for a solution. It's funny because your question itself solved my issue! I did this ...
<button
onClick={() =>
(stream.getVideoTracks()[0].enabled =
!stream.getVideoTracks()[0].enabled)
}
>
By the way as you might have noticed (from the "onClick") that I am doing this on react. In your case, however, I think you should get the video stream from the device first and set it into a variable. Then call the user with that stream variable and when you need it, you can stop and start the streaming by doing stream.getVideoTracks()[0].enabled = !stream.getVideoTracks()[0].enabled. But, make sure you have one source of truth for the stream and always reference it from there. I did this in react and it's working for me. Hope that it does for you too!

Related

How can i add a custom video in broadcast in opentok

I wanted to add video while broadcasting.
To do this i am refering this link :
https://github.com/opentok/opentok-web-samples/tree/main/Publish-Video
After OT.initPublisher i am publishing this publisher in to session session.publish
But video is not showing in livestreaming.
Can anybody help me with this?
We can Publish custom audio source and video source from Video Element, using the captureStream() / mozCaptureStream() methods
Like mentioned in the below code snip
const contentVideoElement = document.createElement('VIDEO');
let screenPublisher = null;
contentVideoElement.autoplay = true;
contentVideoElement.controls = true;
contentVideoElement.classList.add('cameraContainer');
const url = URL.createObjectURL(file); // choose video file from input file control
contentVideoElement.src = url;
try {
await contentVideoElement.play();
} catch (error) {
console.log(error);
return;
}
let mediaStream = null;
if (contentVideoElement.captureStream) {
mediaStream = contentVideoElement.captureStream();
} else if (contentVideoElement.mozCaptureStream) {
mediaStream = contentVideoElement.mozCaptureStream();
} else {
console.error('Stream capture is not supported');
mediaStream = null;
return;
}
const videoTracks = mediaStream.getVideoTracks();
const audioTracks = mediaStream.getAudioTracks();
if (videoTracks.length > 0 && audioTracks.length > 0) {
const el = document.createElement('div');
screenPublisher = window.OT.initPublisher(
'content-video-element-id',
{
insertMode: 'append',
videoSource: videoTracks[0],
audioSource: audioTracks[0],
fitMode: 'contain', // Using default
width: '100%',
height: '100%',
showControls: false,
name:`Guest (Video)`,
},
(error) => {
if (error) {
contentVideoElement.pause();
console.log(error);
} else {
contentVideoElement.play();
openToksession.publish(screenPublisher, (error) => {
if (error) {
console.log(error);
} else {
// write here code after success publish video stream
}
});
}
},
);
screenPublisher.on({
streamDestroyed: ({ stream }) => {
contentVideoElement.pause();
},
});
contentVideoElement.addEventListener(
'ended',
() => {
console.log('Shared video ended');
},
false,
);
}
For capture MediaStream in reactjs: click here

How do I test the useEffect React hook when it includes a 'document.addEventListener' inside it?

Here is my useEffect Call:
const ref = useRef(null);
useEffect(() => {
const clickListener = (e: MouseEvent) => {
if (ref.current.contains(e.target as Node)) return;
closePopout();
}
document.addEventListener('click', clickListener);
return () => {
document.removeEventListener('click', clickListener);
closePopout();
}
}, [ref, closePopout]);
I'm using this to control a popout menu. When you click on the menu icon to bring up the menu it will open it up. When you click anywhere that isn't the popout it closes the popout. Or when the component gets cleaned up it closes the popout as well.
I'm using #testing-library/react-hooks to render the hooks:
https://github.com/testing-library/react-hooks-testing-library
We are also using TypeScript so if there is any TS specific stuff that would be very helpful as well.
Hopefully this is enough info. If not let me know.
EDIT:
I am using two companion hooks. I'm doing quite a bit in it and I was hoping to simplify the question but here is the full code for the hooks. The top hook (useWithPopoutMenu) is called when the PopoutMenu component is rendered. The bottom one is called inside the body of the PopoutMenu component.
// for use when importing the component
export const useWithPopoutMenu = () => {
const [isOpen, setIsOpenTo] = useState(false);
const [h, setHorizontal] = useState(0);
const [v, setVertical] = useState(0);
const close = useCallback(() => setIsOpenTo(false), []);
return {
isOpen,
menuEvent: {h, v, isOpen, close} as PopoutMenuEvent,
open: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
setIsOpenTo(true);
setHorizontal(e.clientX);
setVertical(e.clientY);
},
close
};
}
type UsePopoutMenuArgs = {
menuEvent: PopoutMenuEvent
padding: number
tickPosition: number
horizontalFix: number | null
verticalFix: number | null
hPosition: number
vPosition: number
borderColor: string
}
// for use inside the component its self
export const usePopoutMenu = ({
menuEvent,
padding,
tickPosition,
horizontalFix,
verticalFix,
hPosition,
vPosition,
borderColor
}: UsePopoutMenuArgs) => {
const ref = useRef() as MutableRefObject<HTMLDivElement>;
useEffect(() => {
const handleClick = (e: MouseEvent) => {
if (ref.current.contains(e.target as Node)) return;
menuEvent.close();
}
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
menuEvent.close();
}
}, [menuEvent.close, ref]);
const menuContainerStyle = useMemo(() => {
const left = horizontalFix || menuEvent.h;
const top = verticalFix || menuEvent.v;
return {
padding,
left,
top,
marginLeft: hPosition,
marginTop: vPosition,
border: `1px solid ${borderColor}`
}
}, [
padding,
horizontalFix,
verticalFix,
menuEvent,
hPosition,
vPosition,
borderColor
]);
const backgroundArrowStyle = useMemo(() => {
return {
marginLeft: `-${padding + 6}px`,
marginTop: 4 - padding + tickPosition,
}
},[padding, tickPosition]);
const foregroundArrowStyle = useMemo(() => {
return {
marginLeft: `-${padding + 5}px`,
marginTop: 4 - padding + tickPosition,
}
},[padding, tickPosition]);
return {
ref,
menuContainerStyle,
backgroundArrowStyle,
foregroundArrowStyle
}
}
Here is the component:
type PopoutMenuProps = {
children: React.ReactChild | React.ReactChild[] // normal props.children
menuEvent: PopoutMenuEvent
padding?: number // padding that goes around the
tickPosition?: number // how far down the tick is from the top
borderColor?: string // border color
bgColor?: string // background color
horizontalFix?: number | null
verticalFix?: number | null
vPosition?: number
hPosition?: number
}
const Container = styled.div`
position: fixed;
display: block;
padding: 0;
border-radius: 4px;
background-color: white;
z-index: 10;
`;
const Arrow = styled.div`
position: absolute;
`;
const PopoutMenu = ({
children,
menuEvent,
padding = 16,
tickPosition = 10,
borderColor = Style.color.gray.medium,
bgColor = Style.color.white,
vPosition = -20,
hPosition = 10,
horizontalFix = null,
verticalFix = null
}: PopoutMenuProps) => {
const binding = usePopoutMenu({
menuEvent,
padding,
tickPosition,
vPosition,
hPosition,
horizontalFix,
verticalFix,
borderColor
});
return (
<Container ref={binding.ref} style={binding.menuContainerStyle}>
<Arrow style={binding.backgroundArrowStyle}>
<Left color={borderColor} />
</Arrow>
<Arrow style={binding.foregroundArrowStyle}>
<Left color={bgColor} />
</Arrow>
{children}
</Container>
);
}
export default PopoutMenu;
Usage is something like this:
const Parent () => {
const popoutMenu = useWithPopoutMenu();
return (
...
<ComponentThatOpensThePopout onClick={popoutMenu.open}>...
...
{popoutMenu.isOpen && <PopoutMenu menuEvent={menuEvent}>PopoutMenu Content</PopoutMenu>}
);
}
Do you need to test the hook in isolation?
Testing the component that consumes the hook would be much easier and it would also be a more realistic test, pseudo code below:
render(<PopoverConsumer />);
userEvent.click(screen.getByRole('button', { name: 'Menu' });
expect(screen.getByRole('dialog')).toBeInTheDocument();
userEvent.click(screen.getByText('somewhere outside');
expect(screen.getByRole('dialog')).not.toBeInTheDocument();

Nuxt SSR return loaded image dimensions to server

I'm trying to preview a profile photo on an existing img element my problem is that the new image dimension become undefined outside of the img load function.
How can i pass these dimensions to the server so that i can properly resize the img element?
I've tried using Vuex Store as well to pass the dimensions but with the same undefined results.
I also have a function that listens for resize and after a user changes the window the img is resized properly, however i'm trying to do this without the event trigger. Even when i try to manually trigger the resize event with jQuery it does not resize. I'm under the impression that somehow the dimension of the new img source are not being set properly until a resize even refreshes the dimensions?
<b-img id="profilePhoto" v-bind="profile" :src="this.photo" class="profilePhoto" #change="handleResize()"></b-img>
export default {
data() {
return {
errors:{},
profile: {},
fileDimensions: {},
photo: '',
form: this.$vform({
id: '',
name: '',
email: '',
password: '',
role: '',
bio: '',
photo: ''
})
}
}
}
getPhoto(e) {
let file = e.target.files[0];
if (typeof file !== 'undefined'){
let reader = new FileReader()
let limit = 1024 * 1024 * 2
if (file.size > limit) {
return false;
}
reader.onloadend = (file) => {
this.form.photo = reader.result
this.photo = this.form.photo
}
reader.readAsDataURL(file)
$("<img/>",{
load : function(){
// will return dimensions fine if i use alert here
this.fileDimensions = { width:this.width, height:this.height} ;
},
src : window.URL.createObjectURL(file)
});
// becomes undefined outside of the load functions
var aw = this.fileDimensions.width
var ah = this.fileDimensions.height
var ph = $('.profile').height()
var pw = $('.profile').width()
console.log(this.fileDimensions.width)
if (ah>aw){
this.profile = { width: ph, height: 'auto'}
} else if (aw>ah) {
this.profile = { width: 'auto', height: ph}
} else {
this.profile = { width: ph+10, height: pw+10}
}
}
}
I expect to get the dimensions so i can determine how to set the width and height properties for the img element with its new src however the dimension become undefined.
I figured it out through another post. I had to create a promise. async await in image loading
getDimensions(src){
return new Promise((resolve,reject) => {
let img = new Image()
img.onload = () => resolve({ height: img.height, width: img.width })
img.onerror = reject
img.src = src
})
},
getPhoto(e) {
let file = e.target.files[0];
if (typeof file !== 'undefined'){
let reader = new FileReader()
let limit = 1024 * 1024 * 2
if (file.size > limit) {
return false;
}
reader.onloadend = (file) => {
this.form.photo = reader.result
this.photo = this.form.photo
}
reader.readAsDataURL(file)
this.getDimensions(URL.createObjectURL(file))
.then((dimensions) => {
if (process.client){
var ah = $('.profile').height()
var aw = $('.profile').width()
var ph = dimensions.height
var pw = dimensions.width
if (ph>pw){
this.profile = { width: ah+10, height: 'auto'}
} else if (pw>ph) {
this.profile = { width: 'auto', height: ah+10}
} else {
this.profile = { width: ah+10, height: aw+10}
}
}
})
}
}

How to return dimensions of document in Cypress for use in test later

I have a function in Cypress support/index.js that is meant to get the dimensions of the cy.document outerWidth and outerHeight, then return them for future use in a test. My problem is that when the test runs and the values are compared with others the assertion says the values are NaN. I checked by console logging the value at the point of the assertion and it was empty, so I must be doing something wrong, I'm just not sure what. My function is below, any help gratefully received, thanks.
function getViewport() {
var viewport = {}
cy.document().then((doc) => {
let width = Cypress.$(doc).outerWidth()
let height = Cypress.$(doc).outerHeight()
viewport['bottom'] = height
viewport['height'] = height
viewport['left'] = 0
viewport['right'] = width
viewport['top'] = 0
viewport['width'] = width
viewport['x'] = 0
viewport['y'] = 0
}).then(() => {
return viewport
})
return viewport
}
The code that calls getViewport() is
export const getRect = (obj) => {
var rect
if (obj == 'viewport') {
rect = getViewport()
} else {
rect = getElement(obj)
if (Cypress.config('parseLayoutToInt')) { rect = parseAllToInt(rect) }
}
return rect
}
And that is called by a custom command, where subject is prevSubject and the element is the string "viewport"
Cypress.Commands.add('isInside', { prevSubject: true }, (subject, element, expected) => {
var minuend, subtrahend, diff
minuend = getRect(element)
subtrahend = getRect(subject)
diff = getRectDiff(minuend, subtrahend, expected);
expect(diff).to.deep.equal(expected);
})
Like #NoriSte said, the cy commands are asynchronous thus you can't mix them with sync code.
What you want to do is something like:
function getViewport() {
return cy.document().then( doc => {
rect = /* do something synchronous */
return rect;
});
}
Anyway, to answer the original question (in the title), there's a couple of patterns I use to store a value for later use in cypress:
wrap next commands in the then callback:
cy.document().then( doc => {
return doc.documentElement.getBoundingClientRect();
}).then( viewportRect => {
cy.doSomething(viewportRect);
cy.doSomethingElse();
});
cache to a variable and access the cached value from inside an enqueued command:
let viewportRect;
cy.document().then( doc => {
return doc.documentElement.getBoundingClientRect();
}).then( rect => viewportRect = rect );
cy.doSomething();
// this is important -- you need to access the `viewportRect`
// asynchronously, else it will be undefined at the time of access
// because it's itself assigned asynchronously in the first command'd callback
cy.then(() => {
doSomething(viewportRect);
});
Ad the actual problem in your question (if I understood it correctly), I've made a solution you can learn from:
const getRect = (selector) => {
if (selector == 'viewport') {
return cy.document().then( doc => {
return doc.documentElement.getBoundingClientRect();
});
} else if ( typeof selector === 'string' ) {
return cy.get(selector).then( $elem => {
return $elem[0].getBoundingClientRect();
});
// assume DOM elem
} else {
return cy.wrap(selector).then( elem => {
return Cypress.$(elem)[0].getBoundingClientRect();
});
}
};
const isInside = (containerRect, childRect) => {
if ( !containerRect || !childRect ) return false;
return (
childRect.top >= containerRect.top &&
childRect.bottom <= containerRect.bottom &&
childRect.left >= containerRect.left &&
childRect.right <= containerRect.right
);
};
Cypress.Commands.add('isInside', { prevSubject: true }, (child, container, expected) => {
return getRect(child).then( childRect => {
getRect(container).then( containerRect => {
expect(isInside(containerRect, childRect)).to.equal(expected);
});
});
});
describe('test', () => {
it('test', () => {
cy.document().then( doc => {
doc.body.innerHTML = `
<div class="one"></div>
<div class="two"></div>
<style>
.one, .two {
position: absolute;
}
.one {
background: rgba(255,0,0,0.3);
width: 400px;
height: 400px;
}
.two {
background: rgba(0,0,255,0.3);
width: 200px;
height: 200px;
}
</style>
`;
});
cy.get('.two').isInside('.one', true);
cy.get('.one').isInside('.two', false);
});
it('test2', () => {
cy.document().then( doc => {
doc.body.innerHTML = `
<div class="one"></div>
<div class="two"></div>
<style>
body, html { margin: 0; padding: 0 }
.one, .two {
position: absolute;
}
.one {
background: rgba(255,0,0,0.3);
width: 400px;
height: 400px;
}
.two {
background: rgba(0,0,255,0.3);
width: 200px;
height: 200px;
left: 300px;
}
</style>
`;
});
cy.get('.two').isInside('.one', false);
cy.get('.one').isInside('.two', false);
});
it('test3', () => {
cy.document().then( doc => {
doc.body.innerHTML = `
<div class="one"></div>
<style>
body, html { margin: 0; padding: 0 }
.one {
position: absolute;
background: rgba(255,0,0,0.3);
width: 400px;
height: 400px;
left: -100px;
}
</style>
`;
});
cy.get('.one').isInside('viewport', false);
});
});
Why there is a synchronous return in your getViewport function? I'm speaking about the last return viewport
function getViewport() {
var viewport = {}
cy.document().then((doc) => {
...
})
return viewport // <-- ?????
}
doing so, all the cy.document().then((doc) etc. code is useless.
I don't know if this is the problem, but I can't run your code locally because it misses a lot of functions. Could you share a "working” GitHub repo to make some more tests?
I ran into this problem as well, and opted for a solution with async/await:
function getDocument() {
return new Promise(resolve => {
cy.document().then(d => {
console.log('deeee', d);
resolve(d);
});
});
}
describe('Stuff', () => {
it('Sees the toasty character', async () => {
const document = await getDocument();
// Your test code here
});
});
Even though Cypress commands aren't really promises, you can create your own promise, and resolve it when ready. Then await that promise in your test code.
Hope it helps!

Add a Youtube embed video on Windows 10 Universal App

I'm trying to embed a YouTube video into my Windows 10 Universal App. I know there is ways to do it that goes against the terms on YouTube, but is there a way to do this that doesn't go against them?
I've tried the following code, and I was able to get a YouTube player up. But the video doesn't load.
Under Initialize
string html = #"<style> body{margin:0; padding:0;} iframe{width:100%;height:480px;}#media screen and (max-width:300px) { iframe{width:100%;height:180px;}} </style><iframe style=""padding:0px;margin-bottom:-20px;"" src=""https://www.youtube.com/embed/OLE5oAZanA4" + #" ? rel=0"" frameborder=""0"" allowfullscreen></iframe>";
videoView.NavigateToString(html);
UI code
<WebView Name="videoView" HorizontalAlignment="Left" Height="297" Margin="466,150,0,0" Grid.Row="1" VerticalAlignment="Top" Width="441"/>
For anyone that does use MyToolkit (which goes against YT terms). Is the views still being tracked when you use this method?
try this
StringBuilder stringbuild = new StringBuilder();
stringbuild.Append("<!DOCTYPE html>");
stringbuild.Append("<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">");
stringbuild.Append("<head> <meta charset=\"utf-8\"/> <title></title> </head>");
stringbuild.Append("<body>");
stringbuild.Append(" <style> iframe {border: none;}");
stringbuild.Append(" html, body {margin: 0px; padding: 0px; border: 0px; width: 100%; height: 100%; overflow:hidden;} </style>");
stringbuild.Append(" <div id=\"player\" style=\"width:200px; height:400px;\"></div>");
stringbuild.Append("<script>");
stringbuild.Append("var tag = document.createElement('script');");
stringbuild.Append("tag.src = \"https://www.youtube.com/iframe_api\";");
stringbuild.Append("var firstScriptTag = document.getElementsByTagName('script')[0];");
stringbuild.Append(" firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);");
stringbuild.Append(" function onYouTubeIframeAPIReady() {window.external.notify(\"YoutubePlayerLoadCompleted\"); }");
stringbuild.Append(" var width; var height;");
stringbuild.Append(" function setwidth(incoming) { width = incoming; document.getElementById(\"player\").style.width = incoming+ 'px'; }");
stringbuild.Append("function setheight(incoming) { height = incoming; document.getElementById(\"player\").style.height = incoming +'px'; }");
stringbuild.Append("var player;");
stringbuild.Append(" function loadplayer(incomming) { player = new YT.Player('player', { height: height, width: width, playerVars: { 'fs': 1 }, videoId: incomming, events: { 'onReady': onPlayerReady, 'onStateChange': onPlayerStateChange } }); var element = document.getElementById('videoframe'); }");
stringbuild.Append("function onPlayerReady(event) { }");
stringbuild.Append("function onPlayerStateChange(event) {}");
stringbuild.Append("function play() { if (!player){} else { try { player.playVideo();} catch(err){window.external.notify(err.message);} }}");
stringbuild.Append(" function pause() { player.pauseVideo(); }");
stringbuild.Append("</script> </body> </html>");
string ts = stringbuild.ToString();
webview.NavigateToString(ts);
webview.ScriptNotify += async delegate (object sender1, NotifyEventArgs e1)
{
var jsScriptValue = e1.Value;
if (jsScriptValue.ToString().Equals("YoutubePlayerLoadCompleted"))
{
await webview.InvokeScriptAsync("setwidth", new string[] {"500" });
await webview.InvokeScriptAsync("setheight", new string[] {"400" });
await webview.InvokeScriptAsync("loadplayer", new string[] { "_P9lGTiiXW0" });
}
};

Resources