Vue.js Application - video element denies streaming on mobile - node.js

I'm working on a Vue.js web app that needs to support video streaming. The backend is a Node.js app. It's pulling the videos from an S3 bucket and sending an unbuffered stream to the client. Here's the frontend code:
<template>
<div class="page-container">
<div v-if="currentVideo" class="pageContent">
<section-head>{{ currentVideo.name }}</section-head>
<p>{{ currentVideo.description }}</p>
<video
v-if="videoUrl"
:poster="currentVideo.thumbnail"
playsinline
controls
controlslist="nodownload"
class="stream"
type="video/mp4"
:key="videoUrl"
:src="videoUrl"
/>
<section-head>See More</section-head>
<gallery />
</div>
<h1 v-else class="sorry">
If you're seeing this message, you may have accidently gone to the wrong page.
Please go to the <router-link to="/">Home</router-link> page.
</h1>
</div>
</template>
<script>
import sectionHead from '../components/atoms/Header/SectionHead.vue';
import config from '../../config.js';
import gallery from '../components/molecules/Gallery/Gallery.vue';
export default {
name: 'Stream',
components: { sectionHead, gallery },
computed: {
currentVideo() {
return this.$store.state.currentVideo;
},
videoUrl() {
return 'https://' + config.currentEnvAPI() + '/stream/' + this.currentVideo.video;
},
},
};
</script>
<style lang="scss" scoped>
#import '../styles/_variables.scss';
.page-container {
margin: 0 auto;
p {
text-align: center;
font-size: 25px;
margin: 0 0 6px;
}
}
.stream {
display: block;
margin: 0 auto;
width: 750px;
height: auto;
outline: none;
#include tablet {
width: 650px;
}
#include phone {
width: 100%;
}
}
.sorry {
text-align: center;
padding: 120px;
}
</style>
And I'm setting and getting the video object from VueX here:
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions.js';
import mutations from './mutations.js';
Vue.use(Vuex);
const state = {
videoList: [],
videoObjects: [],
loadingData: false,
currentVideo: null,
currentGallery: [],
};
export default new Vuex.Store({
state,
actions,
mutations,
});
This is the route that I'm calling on the backend:
app.get('/stream/:video', async (req, res) => {
let videoParams = {
Bucket: BUCKET_NAME,
Key: req.params.video,
};
S3.getObject(videoParams)
.on('httpHeaders', function (statusCode, headers) {
res.set('Content-Length', headers['content-length']);
res.set('Content-Type', headers['content-type']);
res.set('Accept-Ranges', headers['accept-ranges']);
this.response.httpResponse.createUnbufferedStream()
.pipe(res);
})
.send();
});
As far as I can tell, this is a good implementation because it works fine on desktop, both locally and deployed. I only have issues on mobile browsers. I've tried both Chrome and Safari on two different iPhones (no access to an Android device). This is all I see for all of my videos:
I also took the time to set up an SSL certificate for all of my endpoints, so I know the videos are streaming over https. I was thinking that the size of the videos (between 250 - 550 MB) may be the issue, but I also noticed that even though the element is disabled, the connection still transfers the whole thing.
I would think if it was a server side issue that it wouldn't send the whole file, but it does. So wouldn't the issue be client side? I can't figure out why it's not working though. The files are mp4 and I'm sure that they should work because I've tried other links to test with mp4 videos and they've worked. I've changed up the element attributes a lot too. I've tried both with and without playsinline, autoplay, and muted. I've tried having the source element as a child of the video element and that still didn't work. I'm also not getting any console errors, so I can't figure out what the actual problem is and I'm not sure how else I can troubleshoot this.

the h.264 profile of your example file in the comments is "high" but ios safari only supports "base". if you open the file directly in safari, it won't play either.
see https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW9

Related

Empty Div Preventing Interaction with amCharts5 MapChart on Vue3

I decided to dip my toes in Vue and have had an idea for a website for a while which I'd like to use amCharts5 for.
I had some issues initially as all the info I could find was related to Vue2, but I think I've somewhat wrapped my head around Vue3 and its composition API.
The MapChart is created, however there is always a div slapped on top of it which prevent any interaction. If I delete this element via DevTools, the MapChart becomes interactive.
I've tried debugging this and commenting sections of the code out, regardless this div is always created. And I simply can't figure out if it's injected by Vue or if amCharts 5 is the culprit.
The highlighted element is the one I must delete for it to become interactive.
Here's how the component is setup;
<template>
<div class="testClass" ref="chartdiv">
</div>
</template>
<script setup lang="ts">
import * as am5 from "#amcharts/amcharts5";
import * as am5map from "#amcharts/amcharts5/map";
import am5geodata_worldLow from "#amcharts/amcharts5-geodata/worldLow";
import am5themes_Animated from '#amcharts/amcharts5/themes/Animated';
import { ref, onMounted, onUnmounted } from "vue";
const chartdiv = ref<HTMLElement | null>()
var root!: am5.Root;
onMounted(() => {
if (chartdiv.value) {
// Create the Root
var root = am5.Root.new(chartdiv.value);
// Setup the MapChart
var chart = root.container.children.push(
am5map.MapChart.new(root, {
panX: "rotateX",
panY: "rotateY",
projection: am5map.geoOrthographic(),
centerMapOnZoomOut: false
})
);
// Setup Animations
root.setThemes([
am5themes_Animated.new(root)
]);
// Create MapPolygons
var polygonSeries = chart.series.push(
am5map.MapPolygonSeries.new(root, {
geoJSON: am5geodata_worldLow
})
);
// Setup MapPolygon Styling
polygonSeries.mapPolygons.template.setAll({
tooltipText: "{name}",
fill: am5.color("#909090")
});
// Setup MapPolygon Hover Styling
polygonSeries.mapPolygons.template.states.create("hover", {
fill: am5.color("#FF0000"),
stroke: am5.color("#00FF00"),
strokeWidth: 2
});
polygonSeries.mapPolygons.template.events.on("click", function(event) {
//console.log("Clicked: {0}", event.target);
});
// Setup Background
var backgroundSeries = chart.series.unshift(
am5map.MapPolygonSeries.new(root, {})
);
backgroundSeries.mapPolygons.template.setAll({
fill: am5.color(0x2c84d0),
stroke: am5.color(0x2c84d0)
});
backgroundSeries.data.push({
geometry: am5map.getGeoRectangle(90, 180, -90, -180)
});
}
});
onUnmounted(() => {
if (root) {
root.dispose();
}
});
</script>
<style scoped>
.testClass {
width: 50vw;
height: 50vh;
}
</style>
When you create a Vite-powered Vue project, it automatically creates a bunch of CSS files for you. One of those is base.css.
Inside this file, you'll find these lines which causes all the headache;
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
position: relative;
font-weight: normal;
}
Removing those lines will fix the issue.

Place Text over a full screen video

Working on a little self project and having some trouble getting text placed on top of my background video.
At present, the code is sitting as follows:
<div class="video_container">
<div class="contentContainer">
<div class="skipButton">
<h1>Skip</h1>
</div>
<video id="tgVideo" autoplay loop>
<source src="videos/bgvidm4v.m4v" preload="none">
</video>
</div>
</div>
I am making the video full screen and keep this way when displaying on different size monitors by using the following JS
$(document).ready(function () {
var vid = $('video');
var vid_w_orig = 1280;
var vid_h_orig = 720;
// re-scale image when window resizes
$(window).resize(function () {
//Get the parent element size
var container_w = vid.parent().width();
var container_h = vid.parent().height();
//Use largest scale factor of horizontal/vertical
var scale_w = container_w / vid_w_orig;
var scale_h = container_h / vid_h_orig;
var scale = scale_w > scale_h ? scale_w : scale_h;
//Scale the video to fit any size screen
vid.width(scale * vid_w_orig);
vid.height(scale * vid_h_orig);
});
//Trigger re-scale of the video on pageload
$(window).trigger('resize');
});
This combination is working flawlessly for me so far. Only issue is getting the video to run on Android/iOS, but I think that's a limitation of the device.
What I am in need of is adding a piece of text for now that a user can click on to bring them away from the video. I am adding the href to the button after I get the text to display on top of the video.
I have found some tutorials online and have tried the below
.video_container .contentContainer {
position: absolute;
width: 100%;
height:100%;
background:#000;
opacity:0.5;
z-index:999;
}
.video_container .contentContainer .skipButton {
width:100%;
text-align:center;
}
.video_container .contentContainer .skipButton h1 {
color:#FFF;
text-transform:uppercase;
}
This is working for the most part, where I can see the text for a split second before it disappears behind the video.
Anyone have any tips for me?
Cheers!
You are setting the whole container to z-index: 999, this element .contentContainer contains also the video element. So I would put z-index only on the text containers alone with non-static position in order z-index to take effect.
.video_container .contentContainer {
position: absolute;
width: 100%;
height:100%;
background:#000;
opacity:0.5;
z-index:999; // not needed
}
.video_container .contentContainer .skipButton {
width:100%;
text-align:center;
position: relative;
z-index: 1000;
}
.video_container .contentContainer .skipButton h1 {
color:#FFF;
text-transform:uppercase;
position: relative;
z-index: 1000;
}

Trouble verify google invisible recaptcha with node.js

I am using the npm recaptcha verify plugin:
https://www.npmjs.com/package/recaptcha-verify
In my react app I am using
https://www.npmjs.com/package/react-google-invisible-recaptcha
At the top of my node app code:
var Recaptcha = require('recaptcha-verify');
var recaptcha = new Recaptcha({
secret: 'secret_key',
verbose: true
});
And then the route that works fine to send the email without recaptcha...
router.post('/mailer/recaptcha', function(req, res) {
var userResponse = req.query['g-recaptcha-response'];
console.log("user response: ", userResponse)
recaptcha.checkResponse(userResponse, function(error, response){
if(error){
// an internal error?
res.status(400).render('400', {
message: error.toString()
});
return;
}
if(response.success){
res.status(200).send('the user is a HUMAN :)');
// save session.. create user.. save form data.. render page, return json.. etc.
}else{
res.status(200).send('the user is a ROBOT :(');
// show warning, render page, return a json, etc.
}
});
In the form, using the react plugin, I am trying to follow the documentation as well, and it currently looks like this.
<Recaptcha
ref={ ref => this.recaptcha = ref }
sitekey="site_key"
onResolved={ this.testRecaptcha } />
The onResolved function attempts to validate the Recaptcha. this.testRecaptcha is a function that dispatches to our node route as seen above. In that route, where I console.log the userResponse,, I am getting undefined. That appears to be the main issue here, I think. The req also logs out all of the items in my form as part of the req.body, but nothing indicates that the recaptcha field is actually there.
testRecaptcha(e) {
let myObject = Object.assign({}, this.state.form, { sentFrom: 'contact', sendTo: this.state.sendTo });
this.props.dispatch(actions.sendTestToMailer(myObject));
}
When I inspect the code that is output from the recaptcha component it looks like this:
<div style="display: none;"><div class="grecaptcha-badge" style="width: 256px; height: 60px; transition: right 0.3s ease; position: fixed; bottom: 14px; right: -186px; box-shadow: gray 0px 0px 5px;"><div class="grecaptcha-logo"><iframe src="https://www.google.com/recaptcha/api2/anchor?k=sitekey&co=aHR0cDovL2xvY2FsaG9zdDo4MDgx&hl=en&v=v1514934548259&size=invisible&cb=oh5n23icp55m" width="256" height="60" role="presentation" frameborder="0" scrolling="no" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation allow-modals allow-popups-to-escape-sandbox"></iframe></div><div class="grecaptcha-error"></div><textarea id="g-recaptcha-response" name="g-recaptcha-response" class="g-recaptcha-response" style="width: 250px; height: 40px; border: 1px solid #c1c1c1; margin: 10px 25px; padding: 0px; resize: none; display: none; "></textarea></div></div>
(where sitekey is the actual key -- not the text 'sitekey)
but, i receive the following error from the node.js app
{ success: false, 'error-codes': [ 'invalid-input-response' ] }
It seems I am not pushing the recaptcha data into this.state.form, but I am not sure what object needs to be pushed into that or if that is even the issue.
Does anyone have any insight on this? Is there an easier way to verify the invisible recaptcha? There is little to no documentation or working examples with every step to take here with node and react. Hopefully someone can help me and anyone else in a similar situtation?
------- EDIT -------------------------------------------------------
Based on feedback from trixn, made these changes and its almost working...
testRecaptcha(e) {
let recaptchaResponse = this.recaptcha.getResponse();
let myObject = Object.assign({}, this.state.form, { sentFrom: 'contact', sendTo: this.state.sendTo, recaptchaResponse:recaptchaResponse });
this.props.dispatch(actions.sendTestToMailer(myObject));
}
AND...
in the node backend:
var userResponse = req.body.recaptchaResponse;
recaptcha.checkResponse(userResponse, function(error, response){ etc..});
but... I am now getting this error.
Error parsing the response to an object. AND 'No default engine was specified and no extension was provided.'
You need to get the response token of your solved reCaptcha by accessing this.recaptcha.getResponse() in your onResolved callback then add that to your POST data and validate that in your node backend.

Vuex & Websockets

So currently I am working with VueJS 2 and I am very new with it. Now I was getting some help with some other people, but I am still stuck.
Here is what I want to achieve (example - closely linked to what I want):
I have a NodeJS application that listens on WebSockets. The application listens for connections via WebSocket and will take JSON data, with a command and then a data object with any content needed for that command.
The command for example could be login, and the data be username and password. The login function on the NodeJS application will then take this data, do what it needs and then return it back over the socket, whether it was successful or not, and maybe include an ID and some user information for Vuex to pickup and place in it's state, for the front-end of the application to pick up/use.
Currently I am using this boiler plate: https://github.com/SimulatedGREG/electron-vue
Which has served me very well as a learning curve, due to me wanting to use Vue and Vuex to manage my application and then use WebSockets for managing data to and from the data server.
So if you look at the link I sent in app/src/renderer/ (this is where the main code is for vue and vuex).
A friend of mine added the following code for me as an example and I am stuck trying to get it into vuex as actions and mutations. He made it all in one vue component, so I am struggling on how it works with vuex. As I want to be able to access the (example) loginUser action from anywhere in the application (uses routes for multiple pages/views).
So in the MyApp/app/src/renderer/components/LandingPageView.vue
<template>
<div>
<img src="./LandingPageView/assets/logo.png" alt="electron-vue">
<h1>Welcome.</h1>
<current-page></current-page>
<websocket-output></websocket-output>
<versions></versions>
<links></links>
</div>
</template>
<script>
import CurrentPage from './LandingPageView/CurrentPage'
import Links from './LandingPageView/Links'
import Versions from './LandingPageView/Versions'
import WebsocketOutput from './LandingPageView/WebsocketOutput'
export default {
components: {
CurrentPage,
Links,
Versions,
WebsocketOutput
},
name: 'landing-page'
}
</script>
<style scoped>
img {
margin-top: -25px;
width: 450px;
}
</style>
That is the updated file for that, and then below is the code for the MyApp/app/src/renderer/components/LandingPageView/WebsocketOutput.vue
<template>
<div>
<h2>Socket output:</h2>
<p v-text="socket_out"></p>
</div>
</template>
<script>
var ws = require("nodejs-websocket")
export default {
data () {
return {
socket_out: "connecting to the websocket server..."
}
},
mounted () {
const parent = this
var connection = ws.connect("ws://dannysmc.com:9999", {}, function (conn) {})
connection.on("text", function (text) {
console.log('Text received: ' + text)
parent.socket_out = text
})
connection.on("connect", function () {
connection.send('yo')
})
},
created () {
// Set $route values that are not preset during unit testing
if (process.env.NODE_ENV === 'testing') {
this.$route = {
name: 'websocket-output',
path: '/websocket-output'
}
}
}
}
</script>
<style scoped>
code {
background-color: rgba(46, 56, 76, .5);
border-radius: 3px;
color: #fff;
font-weight: bold;
padding: 3px 6px;
margin: 0 3px;
vertical-align: bottom;
}
p {
line-height: 24px;
color: red;
}
</style>
Everything else is just the boiler plate that you see, so if anyone is willing to help me and give me some tips of what to read that explains this or anything else? as I can't find much information on it unfortunately.
I have an electron application that uses Vue and a websocket for information and here is how I set mine up.
I have a store defined that also actually creates and sets up the websocket.
Store.js
const socket = require("socket-library") // Take your pick of socket libs
const mySocket = new socket(...)
mySocket.on("message", message => store.handleMessage(message))
...other handlers...
const store = {
handleMessage(message){
// do things with the message
}
}
export default store
Renderer.js
import store from "./store"
new Vue({
data:{
store
}
})
This exposes my store at the root level of my Vue and allows me to pass data to components, or what have you. The store manages all the incoming information from the websocket.
With you wanting to use Vuex, you could do essentially the same thing, where Vuex would be your store and when messages come in over the socket, you just pass them to Vuex.
mySocket.on("message", msg => vuexStore.dispatch("onSocketMessage", msg))
and set up your Vue and components to work with Vuex as you typically would.

dojo layout tutorial for version 1.7 doesn't work for 1.7.2

This is sortof a continuation to dojo1.7 layout acting screwy.
So I made some working widgets and tested them out, i then tried altering my work using the tutorial at http://dojotoolkit.org/documentation/tutorials/1.7/dijit_layout/ to make the layout nice. After failing at that in many interesting ways (thus my last question) I started on a new path. My plan is now to implement the layout tutorial example and then stick in my widgets. For some reason even following the tutorial wont work... everything loads then disappears and I'm left with a blank browser window.
Any ideas?
It just struck me that it could be browser compatibility issues, I'm working on Firefox 13.0.1. As far as I know Dojo is supposed to be compatible with this...
anyway, have some code:
HTML:
<body class="claro">
<div
id="appLayout" class="demoLayout"
data-dojo-type="dijit.layout.BorderContainer"
data-dojo-props="design: 'headline'">
<div
class="centerPanel"
data-dojo-type="dijit.layout.ContentPane"
data-dojo-props="region: 'center'">
<div>
<h4>Group 1 Content</h4>
<p>stuff</p>
</div>
<div>
<h4>Group 2 Content</h4>
</div>
<div>
<h4>Group 3 Content</h4>
</div>
</div>
<div
class="edgePanel"
data-dojo-type="dijit.layout.ContentPane"
data-dojo-props="region: 'top'">
Header content (top)
</div>
<div
id="leftCol" class="edgePanel"
data-dojo-type="dijit.layout.ContentPane"
data-dojo-props="region: 'left', splitter: true">
Sidebar content (left)
</div>
</div>
</body>
Dojo Configuration:
var dojoConfig = {
baseUrl: "${request.static_url('mega:static/js')}", //this is in a mako template
tlmSiblingOfDojo: false,
packages: [
{ name: "dojo", location: "libs/dojo" },
{ name: "dijit", location: "libs/dijit" },
{ name: "dojox", location: "libs/dojox" },
],
parseOnLoad: true,
has: {
"dojo-firebug": true,
"dojo-debug-messages": true
},
async: true
};
other js stuff:
require(["dijit/layout/BorderContainer", "dijit/layout/TabContainer",
"dijit/layout/ContentPane", "dojo/parser"]);
css:
html, body {
height: 100%;
margin: 0;
overflow: hidden;
padding: 0;
}
#appLayout {
height: 100%;
}
#leftCol {
width: 14em;
}
I would suggest viewing the 'complete demo' on the tutorial page and then use firebug to compare your code to the example. Often they'll leave out an additional 'demo.css' file or something else that you actually need to stitch everything together.

Resources