This question is being written to be seen by twilio support. If the general public can answer this question that is also beneficial
In my backend python service I have a restful endpoint which handles payloads for users in rooms and their muting behavior
class MuteUnmuteShoChatVideoChatParticipant(APIView):
def post(self, request, *args, **kwargs):
api_key_sid = TWILIO_API_KEY
api_key_secret = TWILIO_SECRET_KEY
client = Client(api_key_sid, api_key_secret)
print('here is the payload')
print(request.data['payload'])
tacos = client.video.rooms(request.data['room']).participants.get(request.data['participant'])\
.subscribe_rules.update(
rules = request.data['payload']
)
print('here is the response')
print(tacos)
return Response(status=HTTP_200_OK)
On the front end in the video room ajax requests with the muting rules are send over.
only audio tracks of remote participants are targeted for muting.
use cases are
mute all audio
unmute all audio
mute individual participant
unmute individual participant
resetnotmuteallusersaudio = [
{"type": "include", "all": true}
];
resetmuteallusersaudio = [
{"type": "include", "kind": "video"},
{"type": "exclude", "kind": "audio"}
];
notmuteallusersaudio: any[] = [
{"type": "include", "all": true}
];
muteallusersaudio: any[] = [
{"type": "include", "kind": "video"},
{"type": "exclude", "kind": "audio"}
];
default muting commands based on if mute all participants or not mute all participants is selected. The resets are just that to reset the arrays when needed.
disable and enable remote audio functions
disableremoteaudio(id) {
let array;
if(this.allmuted){
array = this.muteallusersaudio
}
else {
array = this.notmuteallusersaudio
}
if(array.length > 19){
this.toomanymutes = true;
setTimeout(()=>{
this.toomanymutes = false;
}, 3000);
return;
}
for(let a in array){
if(array[a].publisher != null){
if(array[a].publisher == id){
array.splice(+a, 1);
}
}
}
if(!this.allmuted){
const payload = {"type": "exclude", "publisher": id, "track": "audio"};
array.push(payload);
}
console.log('here is the array payload');
console.log(array);
const payload = {
participant: this.videoclient.localParticipant.sid,
room: this.roomid,
payload: array
};
this.http.post(environment.mute_unmute_shochat_videocall_participant, payload)
.subscribe((req: any)=>{
//cool
});
for(let i = 0; i < this.participantsarray.length; i ++){
if(this.participantsarray[i].sid == id){
this.participantsarray[i].muteaudiobutton = false;
this.participantsarray[i].unmuteaudiobutton = true;
break;
}
}
}
enableremoteaudio(id){
let array;
if(this.allmuted){
array = this.muteallusersaudio
}
else{
array = this.notmuteallusersaudio
}
if(array.length > 19) {
this.toomanymutes = true;
setTimeout(() => {
this.toomanymutes = false;
}, 3000);
return;
}
for(let a in array){
if(array[a].publisher != null){
if(array[a].publisher == id){
array.splice(+a, 1);
}
}
}
if(this.allmuted){
const payload = {"type": "include", "publisher": id, "track": "audio"};
array.push(payload);
}
console.log('here is the array payload');
console.log(array);
const payload = {
participant: this.videoclient.localParticipant.sid,
room: this.roomid,
payload: array
};
this.http.post(environment.mute_unmute_shochat_videocall_participant, payload)
.subscribe((req: any)=>{
//cool
});
for(let i = 0; i < this.participantsarray.length; i ++){
if(this.participantsarray[i].sid == id){
this.participantsarray[i].muteaudiobutton = true;
this.participantsarray[i].unmuteaudiobutton = false;
break;
}
}
}
audio mute all and audio unmute all
audiomuteeveryone(){
this.allmuted = true;
let muttingarray = [];
this.muteallusersaudio = this.resetmuteallusersaudio;
console.log('here is the array payload');
console.log(this.muteallusersaudio);
const payload = {
participant: this.videoclient.localParticipant.sid,
room: this.roomid,
payload: this.notmuteallusersaudio
};
this.http.post(environment.mute_unmute_shochat_videocall_participant, payload)
.subscribe((req: any)=>{
//cool
});
for(let p of this.participantsarray){
p.muteaudiobutton = false;
p.unmuteaudiobutton = true;
muttingarray.push(p.identity);
}
}
audiounmuteeveryone(){
this.allmuted = false;
this.notmuteallusersaudio = this.resetnotmuteallusersaudio;
console.log('here is the array payload');
console.log(this.notmuteallusersaudio );
const payload = {
participant: this.videoclient.localParticipant.sid,
room: this.roomid,
payload: this.notmuteallusersaudio
};
this.http.post(environment.mute_unmute_shochat_videocall_participant, payload)
.subscribe((req: any)=>{
//cool
});
for(let p of this.participantsarray){
p.muteaudiobutton = true;
p.unmuteaudiobutton = false;
}
}
hitting mute all audio results in a subscription command payload of:
[
{
"type": "include",
"kind": "video"
},
{
"type": "exclude",
"kind": "audio"
}
]
with no muting behavior resulted
however hitting mute all and then unmuting a individual participant does result in mute behavior when it shouldn't as we are unmuting the participant and results in a command payload of:
[
{
"type": "include",
"kind": "video"
},
{
"type": "exclude",
"kind": "audio"
},
{
"type": "include",
"publisher": "PA0e7fa842fa4e9ca03327db8649de4cb0",
"track": "audio"
}
]
This obviously makes no sense the mute all behavior should mute everyone, the unmute should then unmute not actually mute. is there something I am missing in my set logic?
Related
I created a opt-in app for potential interims for our company, i worked with Gatsby and for now am quite satisfied with the result. I made it an Progressive Web App as that is fairly easy with the gatsby plugin.
The PWA works great on Android and shows the background video as expected, but on iOS the video doesn't show.
I updated all the packages and dependencies to the last versions but that doesn't change a thing. I tried googling the issue but got a lot of search results off people trying to let a PWA play video in the background when the app is closed (not my case).
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `Afstuderen bij Arcady`,
short_name: `Afstuderen`,
start_url: `/`,
background_color: `#FFF`,
theme_color: `#00a667`,
display: `standalone`,
icon: `src/images/bear_green.png`,
},
},
'gatsby-plugin-offline',
And the content of the service worker
importScripts("workbox-v3.6.3/workbox-sw.js");
workbox.setConfig({modulePathPrefix: "workbox-v3.6.3"});
workbox.core.setCacheNameDetails({prefix: "gatsby-plugin-offline"});
workbox.skipWaiting();
workbox.clientsClaim();
/**
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
* requests for URLs in the manifest.
*/
self.__precacheManifest = [
{
"url": "webpack-runtime-aec2408fe3a97f1352af.js"
},
{
"url": "app-5b624d17337895ddf874.js"
},
{
"url": "component---node-modules-gatsby-plugin-offline-app-shell-js-b97c345e19bb442c644f.js"
},
{
"url": "offline-plugin-app-shell-fallback/index.html",
"revision": "ac0d57f6ce61fac4bfa64e7e08d076c2"
},
{
"url": "0-d2c3040ae352cda7b69f.js"
},
{
"url": "component---src-pages-404-js-cf647f7c3110eab2f912.js"
},
{
"url": "static/d/285/path---404-html-516-62a-0SUcWyAf8ecbYDsMhQkEfPzV8.json"
},
{
"url": "static/d/604/path---offline-plugin-app-shell-fallback-a-30-c5a-BawJvyh36KKFwbrWPg4a4aYuc8.json"
},
{
"url": "manifest.webmanifest",
"revision": "5a580d53785b72eace989a49ea1e24f7"
}
].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
workbox.routing.registerRoute(/(\.js$|\.css$|static\/)/, workbox.strategies.cacheFirst(), 'GET');
workbox.routing.registerRoute(/^https?:.*\.(png|jpg|jpeg|webp|svg|gif|tiff|js|woff|woff2|json|css)$/, workbox.strategies.staleWhileRevalidate(), 'GET');
workbox.routing.registerRoute(/^https?:\/\/fonts\.googleapis\.com\/css/, workbox.strategies.staleWhileRevalidate(), 'GET');
/* global importScripts, workbox, idbKeyval */
importScripts(`idb-keyval-iife.min.js`)
const WHITELIST_KEY = `custom-navigation-whitelist`
const navigationRoute = new workbox.routing.NavigationRoute(({ event }) => {
const { pathname } = new URL(event.request.url)
return idbKeyval.get(WHITELIST_KEY).then((customWhitelist = []) => {
// Respond with the offline shell if we match the custom whitelist
if (customWhitelist.includes(pathname)) {
const offlineShell = `/offline-plugin-app-shell-fallback/index.html`
const cacheName = workbox.core.cacheNames.precache
return caches.match(offlineShell, { cacheName }).then(cachedResponse => {
if (cachedResponse) return cachedResponse
console.error(
`The offline shell (${offlineShell}) was not found ` +
`while attempting to serve a response for ${pathname}`
)
return fetch(offlineShell).then(response => {
if (response.ok) {
return caches.open(cacheName).then(cache =>
// Clone is needed because put() consumes the response body.
cache.put(offlineShell, response.clone()).then(() => response)
)
} else {
return fetch(event.request)
}
})
})
}
return fetch(event.request)
})
})
workbox.routing.registerRoute(navigationRoute)
let updatingWhitelist = null
function rawWhitelistPathnames(pathnames) {
if (updatingWhitelist !== null) {
// Prevent the whitelist from being updated twice at the same time
return updatingWhitelist.then(() => rawWhitelistPathnames(pathnames))
}
updatingWhitelist = idbKeyval
.get(WHITELIST_KEY)
.then((customWhitelist = []) => {
pathnames.forEach(pathname => {
if (!customWhitelist.includes(pathname)) customWhitelist.push(pathname)
})
return idbKeyval.set(WHITELIST_KEY, customWhitelist)
})
.then(() => {
updatingWhitelist = null
})
return updatingWhitelist
}
function rawResetWhitelist() {
if (updatingWhitelist !== null) {
return updatingWhitelist.then(() => rawResetWhitelist())
}
updatingWhitelist = idbKeyval.set(WHITELIST_KEY, []).then(() => {
updatingWhitelist = null
})
return updatingWhitelist
}
const messageApi = {
whitelistPathnames(event) {
let { pathnames } = event.data
pathnames = pathnames.map(({ pathname, includesPrefix }) => {
if (!includesPrefix) {
return `${pathname}`
} else {
return pathname
}
})
event.waitUntil(rawWhitelistPathnames(pathnames))
},
resetWhitelist(event) {
event.waitUntil(rawResetWhitelist())
},
}
self.addEventListener(`message`, event => {
const { gatsbyApi } = event.data
if (gatsbyApi) messageApi[gatsbyApi](event)
})
I expect the iOS PWA (safari) to show the video as it does on Android but instead it gives a grey screen.
I hope some one can help me out or point me in the right direction.
How big is your video ?
Last time I checked, iOS has a limit of 50MB for the cache of a PWA, so if your video is bigger than 50MB, that may be the reason it works only on Android (which doesn't have such restrictions).
I found this blog post that helped me fix this problem
To add Range request handling to gatsby-plugin-offline, I added a script called range-request-handler.js with the following:
// range-request-handler.js
// Define workbox globally
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.0.0/workbox-sw.js');
// Bring in workbox libs
const { registerRoute } = require('workbox-routing');
const { CacheFirst } = require('workbox-strategies');
const { RangeRequestsPlugin } = require('workbox-range-requests'); // The fix
// Add Range Request support to fetching videos from cache
registerRoute(
/(\.webm$|\.mp4$)/,
new CacheFirst({
plugins: [
new RangeRequestsPlugin(),
],
})
);
Then in my gatsby-config.js I configured the plugin to append the above script:
// gatsby-config.js
module.exports = {
// ...
plugins: [
// ...plugins
{
resolve: 'gatsby-plugin-offline',
options: {
appendScript: require.resolve('./range-request-handler.js'),
},
},
// ...plugins
],
// ...
};
Videos now work in the Safari browser for me. If there is a better way to implement this, I am all ears. For now it works as intended
I have the following in the application template:
<vaadin-grid id="directory">
<vaadin-grid-tree-column path="name" header="Name"></vaadin-grid-tree-column>
</vaadin-grid>
The iron-ajax calls the following on a successful response:
getlist(request) {
var myResponse = request.detail.response;
console.log(myResponse);
this.$.directory.items = myResponse;
}
The data that is returned is:
[
{
"name": "apps",
"fullpath": "/db/system/xqdoc/apps",
"children": [
{
"name": "xqDoc",
"fullpath": "/db/system/xqdoc/apps/xqDoc",
"children": [
{
"name": "modules",
"fullpath": "/db/system/xqdoc/apps/xqDoc/modules",
"children": [
{
"name": "config.xqm.xml",
"fullpath": "/db/system/xqdoc/apps/xqDoc/modules/config.xqm.xml"
},
{
"name": "xqdoc-lib.xqy.xml",
"fullpath": "/db/system/xqdoc/apps/xqDoc/modules/xqdoc-lib.xqy.xml"
}
]
}
]
}
]
}
]
The apps shows up, but when I expand the apps node, then xqDoc node doees not show up.
Do I need additional data in the dataset?
Am I missing some coding that is needed?
I have the solution.
<vaadin-grid id="directory" selected-items="{{selected}}">
<vaadin-grid-tree-column path="name" header="Name"item-has-children-path="hasChildren"></vaadin-grid-tree-column>
</vaadin-grid>
I setup the provider using the connectedCallback and not to use an iron-ajax for talking with the server.
connectedCallback() {
super.connectedCallback();
const grid = this.$.directory;
this.$.directory.dataProvider = function(params, callback) {
let url = "/exist/restxq/xqdoc/level" +
'?page=' + params.page + // the requested page index
'&per_page=' + params.pageSize; // number of items on the page
if (params.parentItem) {
url += '&path=' + params.parentItem.fullpath;
}
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var response = JSON.parse(xhr.responseText);
callback(
response.data, // requested page of items
response.totalSize // total number of items
);
};
xhr.open('GET', url, true);
xhr.send();
};
this.$.directory.addEventListener('active-item-changed', function(event) {
const item = event.detail.value;
if (item && item.hasChildren == false) {
grid.selectedItems = [item];
} else {
grid.selectedItems = [];
}
});
}
The web service returns a level of the tree:
{
"totalSize": 2,
"data": [
{
"name": "apps",
"fullpath": "/apps",
"hasChildren": true
},
{
"name": "lib",
"fullpath": "/lib",
"hasChildren": true
}
]
}
The codebase is here: https://github.com/lcahlander/xqDoc-eXist-db
I'm in the process of building an API that will take the JSON object output from a Konva stage and convert that into images on the server-side. I'm making use of the konva-node npm package and it works really well until it comes to loading in remote images that may have been a part of the original "design". I can see from this answer as to how we would solve the problem in the browser, however this doesn't appear to work in the same way in the nodejs implementation of Konva.
An example JSON input is the following:
let json = {
"attrs": {
"width": 600,
"height": 600
},
"className": "Stage",
"children": [{
"attrs": {},
"className": "Layer",
"children": [{
"attrs": {
"src": "https://ichef.bbci.co.uk/onesport/cps/480/cpsprodpb/1859A/production/_101883799_gettyimages-844211624.jpg"
},
"className": "Image"
}]
}, {
"attrs": {},
"className": "Layer",
"children": [{
"attrs": {
"text": "Hello world.",
"x": 50,
"y": 50,
"fontSize": 20,
"fill": "blue"
},
"className": "Text"
}]
}]
}
As you can see, I've subbed in src attributes as a sort of placeholder for when we come to load up the data into a canvas again.
The issue that I'm having is with actually getting those images to load in again once I process the JSON on the server-side.
Here is my current code
var fs = require('fs')
const Konva = require('konva-node')
var Request = require('pixl-request')
let json = {"attrs":{"width":600,"height":600},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{"src":"https://ichef.bbci.co.uk/onesport/cps/480/cpsprodpb/1859A/production/_101883799_gettyimages-844211624.jpg"},"className":"Image"}]},{"attrs":{},"className":"Layer","children":[{"attrs":{"text":"Hello world.","x":50,"y":50,"fontSize":20,"fill":"blue"},"className":"Text"}]}] }
var stage = new Konva.Stage()
let loadedDesign = Konva.Node.create(json)
loadedDesign.find('Image').forEach((imageNode) => {
const imageURL = imageNode.getAttr('src')
var request = new Request();
request.get(imageURL, function(err, resp, data) {
var img = new Konva.window.Image()
img.onerror = err => { throw err }
img.onload = () => {
imageNode.image(img);
imageNode.getLayer().batchDraw();
}
img.src = data;
});
});
loadedDesign.toDataURL({
callback: function(data) {
var base64Data = data.replace(/^data:image\/png;base64,/, '');
fs.writeFile('./images/out.png', base64Data, 'base64', function(err) {
err && console.log(err)
console.log('See out.png')
});
}
});
The current output results in an image with the text on the canvas but the image never makes it in.
You need to load all images, only then use toDataURL(). I guess your image is not visible, because you convert stage to dataURL before images are loaded and rend
var request = new Request();
function loadImage(url) {
return new Promise(resolve => {
request.get(url, function(err, resp, data) {
var img = new Konva.window.Image();
img.onerror = err => {
throw err;
};
img.onload = () => {
resolve(img);
};
img.src = data;
});
});
}
async function run() {
const stage = Konva.Node.create(json);
const images = stage.find('Image');
for (const imageNode of images) {
const imageURL = imageNode.getAttr('src');
const img = await loadImage(imageURL);
imageNode.setImage(img);
}
stage.findOne('Layer').draw();
const data = stage.toDataURL();
var base64Data = data.replace(/^data:image\/png;base64,/, '');
fs.writeFile('out.png', base64Data, 'base64', function(err) {
err && console.log(err);
console.log('See out.png');
});
}
run().catch(e => {
console.error(e);
});
I am unable to insert multiple rows in database using Post method in MVC web API. I have written code for it but when i am testing by inserting multiple rows through postman it is giving error. At line first the variable "delegatetable" shows null due to which error is coming. i am not doing database connection through entity framework, i have created a DelegateTable class.
public HttpResponseMessage Post(List<DelegateTable> delegatetable)
{
try
{
using (var delegateContext = new ShowContext())
{
foreach (DelegateTable item in delegatetable)
{
DelegateTable delegates = new DelegateTable();
delegates.Salutation__c = item.Salutation__c;
delegates.First_Name__c = item.First_Name__c;
delegates.Last_Name__c = item.Last_Name__c;
delegates.Account_Name__c = item.Account_Name__c;
delegates.Contact_Email__c = item.Contact_Email__c;
delegates.Category__c = item.Category__c;
delegates.Conference_Type__c = item.Conference_Type__c;
delegates.Conference_Selection__c = item.Conference_Selection__c;
delegates.Payment_Statuss__c = item.Payment_Statuss__c;
delegates.Barcode__c = item.Barcode__c;
delegateContext.SaveChanges();
}
var message = Request.CreateResponse(HttpStatusCode.Created, delegatetable);
message.Headers.Location = new Uri(Request.RequestUri.ToString());
return message;
}
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
}
}
Json data that i am passing is below
[
{
"attributes": {
"type": "Registration__c",
"url": "/services/data/v43.0/sobjects/Registration__c/a3h8E0000009VuVQAU"
},
"Salutation__c": "Dr.",
"First_Name__c": "Test",
"Last_Name__c": "Test",
"Account_Name__c": "Test",
"Contact_Email__c": "test123#gmail.com",
"Category__c": "Test",
"Conference_Type__c": null,
"Conference_Selection__c": null,
"Payment_Statuss__c": null,
"Barcode__c": "Test"
},
{
"attributes": {
"type": "Registration__c",
"url": "/services/data/v43.0/sobjects/Registration__c/a3hD0000001kEfOIAU"
},
"Salutation__c": "Mr.",
"First_Name__c": "Demo",
"Last_Name__c": "Demo",
"Account_Name__c": "Demo",
"Contact_Email__c": "Demo#gmail.com",
"Category__c": "Demo",
"Conference_Type__c": null,
"Conference_Selection__c": null,
"Payment_Statuss__c": null,
"Barcode__c": null
}
]
You may try to reformat your payload as a JSON array, as the problem might be that the payload cannot be converted to a List.
Try this:
{
"delegates" :
[
{
"attributes": ..., ...
},
{ "attributes": ..., ...
},
...
]
}
When performing a search request to the API, the height & width fields of the thumbnails are not included in the response, even when specified in the fields parameter. Here's an example for a JS object that provides the API parameters:
{
part: 'snippet',
type: 'video',
order: 'relevance',
q: 'test',
fields: 'items(snippet(thumbnails(high(url,height,width))))'
}
Which translates into the following request URL:
https://www.googleapis.com/youtube/v3/search?order=relevance&part=snippet&q=test&fields=items(snippet(thumbnails(high(url%2Cheight%2Cwidth))))&type=video&key={YOUR_API_KEY}
This call yields the following response without the width or height of the thumbnails.
{
"items": [
{
"snippet": {
"thumbnails": {
"high": {
"url": "https://i.ytimg.com/vi/3HKs8WTGzw8/hqdefault.jpg"
}
}
}
},
{
"snippet": {
"thumbnails": {
"high": {
"url": "https://i.ytimg.com/vi/vW_8K_mLtsU/hqdefault.jpg"
}
}
}
},
{
"snippet": {
"thumbnails": {
"high": {
"url": "https://i.ytimg.com/vi/4Yk-jd4BHys/hqdefault.jpg"
}
}
}
},
{
"snippet": {
"thumbnails": {
"high": {
"url": "https://i.ytimg.com/vi/HU9mnag7vSM/hqdefault.jpg"
}
}
}
},
{
"snippet": {
"thumbnails": {
"high": {
"url": "https://i.ytimg.com/vi/pyrH7b0zHwU/hqdefault.jpg"
}
}
}
}
]
}
This similarly does not work for the default or medium thumbnail keys either.
How can these fields be retrieved?
The search endpoint won't return those details. You'll have to take the IDs returned from the search and do another API call to the videos endpoint for the snippet. For instance
https://www.googleapis.com/youtube/v3/videos?part=snippet&id={VIDEO_ID}&key={YOUR_API_KEY}
As per Youtube Date API (v3) Search Method returns as per document but its not working properly.
Now you should try alternative method by the API call of Video.
URL :- https://www.googleapis.com/youtube/v3/videos?part=snippet&id={VIDEO_ID}&key={YOUR_API_KEY}
VIDEO_ID = Return by Search API
YOUR_API_KEY = Google Project API key
Try it
$.get(
"https://www.googleapis.com/youtube/v3/search",{
order:'relevance',
part : 'snippet',
type : 'video',
q: 'test',
key: 'XXXXXXX'},
function(data) {
alert(data.items.length);
$.each( data.items, function( i, item ) {
pid = item.id.videoId;
getVids(pid);
});
}
);
//Get Videos
function getVids(pid){
$.get(
"https://www.googleapis.com/youtube/v3/videos",{
part : 'snippet',
id : pid,
key: 'XXXXXXXX'},
function(data) {
//Code whatever you want
}
);
}