Remote video from Safari iPhone showing black-screen [duplicate] - twilio

iPhone safari version: 11.2.5
Chrome version: Checking for updates Version 80.0.3987.163 (Official Build) (64-bit)
The connection is a peer to peer.
If I can give you an overview it's like -
Video is coming on iPhone from mac safari, Chrome, window laptop
Video is coming on Android Phone from mac safari, Chrome, window laptop
Video is coming on mac safari, Chrome, window laptop from Android Phone
Video is NOT COMING on mac safari, Chrome, window laptop from iPhone
Please let me know if you can help. Thanks in advance.

I solve this issue
createLocalVideoTrack({
video: { width: 1280, height: 720 },
}).then(track => {
if (this.localVideo) {
const element = track.attach();
this.renderer.data.id = track.sid;
this.renderer.setStyle(element, 'width', '25%');
this.renderer.appendChild(this.localVideo.nativeElement, element);
this.attachVideoClass();
}
});
Should be :
this.roomObj.localParticipant.videoTracks.forEach(publication => {
const element = publication.track.attach();
this.renderer.data.id = publication.track.sid;
this.renderer.setStyle(element, 'width', '25%');
this.renderer.appendChild(this.localVideo.nativeElement, element);
})

Twilio developer evangelist here.
I would recommend reading through the developing for Safari 11 article which goes over a bunch of potential issues.

Please check code:
import { Injectable, EventEmitter, ElementRef, Renderer2, RendererFactory2 } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs';
import { connect, createLocalTracks, createLocalVideoTrack } from 'twilio-video';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Http } from '#angular/http';
import { Router } from '#angular/router';
import { BaCustomPreLoader } from './baCustomPreloader.service';
#Injectable()
export class TwilioService {
remoteVideo: ElementRef;
localVideo: ElementRef;
previewing: boolean;
msgSubject = new BehaviorSubject("");
roomObj: any;
roomParticipants;
private renderer: Renderer2;
constructor(
private http: Http,
private router: Router,
private rendererFactory: RendererFactory2,
private baCustomPreLoader: BaCustomPreLoader) {
this.renderer = rendererFactory.createRenderer(null, null);
}
getToken(username): Observable<any> {
return this.http.post('/abc', { uid: 'ashish' });
}
microphone = true;
mute() {
this.roomObj.localParticipant.audioTracks.forEach(function (
audioTrack
) {
audioTrack.track.disable();
});
this.microphone = false;
}
unmute() {
this.roomObj.localParticipant.audioTracks.forEach(function (
audioTrack,
key,
map
) {
audioTrack.track.enable();
});
this.microphone = true;
}
connectToRoom(accessToken: string, options): void {
connect(accessToken, options).then(room => {
this.roomObj = room;
if (!this.previewing && options['video']) {
this.startLocalVideo();
this.previewing = true;
}
this.roomParticipants = room.participants;
room.participants.forEach(participant => {
this.attachParticipantTracks(participant);
});
room.on('participantDisconnected', (participant) => {
this.detachParticipantTracks(participant);
});
room.on('participantConnected', (participant) => {
this.roomParticipants = room.participants;
this.attachParticipantTracks(participant);
participant.on('trackAdded', track => {
const element = track.attach();
this.renderer.data.id = track.sid;
this.renderer.setStyle(element, 'height', '100%');
this.renderer.setStyle(element, 'max-width', '100%');
this.renderer.appendChild(this.remoteVideo.nativeElement, element);
this.attachVideoClass();
this.baCustomPreLoader.hide();
});
});
// When a Participant adds a Track, attach it to the DOM.
room.on('trackAdded', (track, participant) => {
this.attachTracks([track]);
});
// When a Participant removes a Track, detach it from the DOM.
room.on('trackRemoved', (track, participant) => {
this.detachTracks([track]);
});
room.once('disconnected', room => {
room.localParticipant.tracks.forEach(track => {
track.track.stop();
const attachedElements = track.track.detach();
attachedElements.forEach(element => element.remove());
room.localParticipant.videoTracks.forEach(video => {
const trackConst = [video][0].track;
trackConst.stop(); // <- error
trackConst.detach().forEach(element => element.remove());
room.localParticipant.unpublishTrack(trackConst);
});
let element = this.remoteVideo.nativeElement;
while (element.firstChild) {
element.removeChild(element.firstChild);
}
let localElement = this.localVideo.nativeElement;
while (localElement.firstChild) {
localElement.removeChild(localElement.firstChild);
}
this.router.navigate(['thanks']);
setTimeout(() => {
window.location.reload();
}, 1000)
});
});
}, (error) => {
alert(error.message);
});
}
attachParticipantTracks(participant): void {
participant.tracks.forEach(part => {
this.trackPublished(part);
});
}
trackPublished(publication) {
if (publication.isSubscribed)
this.attachTracks(publication.track);
if (!publication.isSubscribed)
publication.on('subscribed', track => {
this.attachTracks(track);
});
}
attachTracks(tracks) {
const element = tracks.attach();
this.renderer.data.id = tracks.sid;
this.renderer.setStyle(element, 'height', '100%');
this.renderer.setStyle(element, 'max-width', '100%');
this.renderer.appendChild(this.remoteVideo.nativeElement, element);
this.attachVideoClass();
this.baCustomPreLoader.hide();
}
startLocalVideo(): void {
createLocalVideoTrack({
video: { width: 1280, height: 720 },
}).then(track => {
if (this.localVideo) {
const element = track.attach();
this.renderer.data.id = track.sid;
this.renderer.setStyle(element, 'width', '25%');
this.renderer.appendChild(this.localVideo.nativeElement, element);
this.attachVideoClass();
}
});
}
detachParticipantTracks(participant) {
this.detachTracks(participant);
}
detachTracks(tracks): void {
tracks.tracks.forEach(track => {
let element = this.remoteVideo.nativeElement;
while (element.firstChild) {
element.removeChild(element.firstChild);
}
});
}
attachVideoClass() {
let remote = document.getElementById('remote');
}
}

Related

How to pass geolocation permission to react-native-webview?

When using the geolcoation api in a react-native-webview, I am asked twice whether the app is allowed to use my current location. How is it possible to forward the already (not) granted permission to the webview?
I am currently using react-native 0.68 and react-native-webview 11.22.
First prompt:
Second prompt:
I should only be asked once for permission to use my current geolocation.
In case somebody faces the same problem, I solved this issue with the following workaround. I injected a custome javacscript into the webview to override the used geolocation api in the webview. My custome script does all the communication with the app. The app returns the geolocation and so the webview doesn't need to aks for permission.
Custome Script
export const getGeoLocationJS = () => {
const getCurrentPosition = `
navigator.geolocation.getCurrentPosition = (success, error, options) => {
window.ReactNativeWebView.postMessage(JSON.stringify({ event: 'getCurrentPosition', options: options }));
window.addEventListener('message', (e) => {
let eventData = {}
try {
eventData = JSON.parse(e.data);
} catch (e) {}
if (eventData.event === 'currentPosition') {
success(eventData.data);
} else if (eventData.event === 'currentPositionError') {
error(eventData.data);
}
});
};
true;
`;
const watchPosition = `
navigator.geolocation.watchPosition = (success, error, options) => {
window.ReactNativeWebView.postMessage(JSON.stringify({ event: 'watchPosition', options: options }));
window.addEventListener('message', (e) => {
let eventData = {}
try {
eventData = JSON.parse(e.data);
} catch (e) {}
if (eventData.event === 'watchPosition') {
success(eventData.data);
} else if (eventData.event === 'watchPositionError') {
error(eventData.data);
}
});
};
true;
`;
const clearWatch = `
navigator.geolocation.clearWatch = (watchID) => {
window.ReactNativeWebView.postMessage(JSON.stringify({ event: 'clearWatch', watchID: watchID }));
};
true;
`;
return `
(function() {
${getCurrentPosition}
${watchPosition}
${clearWatch}
})();
`;
};
Webview
import Geolocation from '#react-native-community/geolocation';
let webview = null;
<WebView
geolocationEnabled={ true }
injectedJavaScript={ getGeoLocationJS() }
javaScriptEnabled={ true }
onMessage={ event => {
let data = {}
try {
data = JSON.parse(event.nativeEvent.data);
} catch (e) {
console.log(e);
}
if (data?.event && data.event === 'getCurrentPosition') {
Geolocation.getCurrentPosition((position) => {
webview.postMessage(JSON.stringify({ event: 'currentPosition', data: position }));
}, (error) => {
webview.postMessage(JSON.stringify({ event: 'currentPositionError', data: error }));
}, data.options);
} else if (data?.event && data.event === 'watchPosition') {
Geolocation.watchPosition((position) => {
webview.postMessage(JSON.stringify({ event: 'watchPosition', data: position }));
}, (error) => {
webview.postMessage(JSON.stringify({ event: 'watchPositionError', data: error }));
}, data.options);
} else if (data?.event && data.event === 'clearWatch') {
Geolocation.clearWatch(data.watchID);
}
}}
ref={ ref => {
webview = ref;
if (onRef) {
onRef(webview)
}
}}
source={ url }
startInLoadingState={ true }
/>

How to fix a Google Maps Error with EAS build on iPhone?

I migrated my Expo SDK 43 Managed Workflow project to an EAS Build.
I use Google Maps as my default map choice for Android and iOS, but I when navigating to a screen with a map I receive this error:
react-native-maps: AirGoogleMaps dir must be added to your xCode project to support GoogleMaps
on iOS. at node_modules/react-native-maps/lib/components/decorateMapComponent.js:27:54 in <anonymous>
When I follow the path laid out in the error to my node_modules file, it takes me to "decorateMapComponent.js" which is a js file that contains the code below.
What needs to be modified to make the EAS on iOS accept Google Maps?
Below is the code:
import PropTypes from 'prop-types';
import { requireNativeComponent, NativeModules, Platform } from 'react-native';
import { PROVIDER_DEFAULT, PROVIDER_GOOGLE } from './ProviderConstants';
export const SUPPORTED = 'SUPPORTED';
export const USES_DEFAULT_IMPLEMENTATION = 'USES_DEFAULT_IMPLEMENTATION';
export const NOT_SUPPORTED = 'NOT_SUPPORTED';
export function getAirMapName(provider) {
if (Platform.OS === 'android') {
return 'AIRMap';
}
if (provider === PROVIDER_GOOGLE) {
return 'AIRGoogleMap';
}
return 'AIRMap';
}
function getAirComponentName(provider, component) {
return `${getAirMapName(provider)}${component}`;
}
export const contextTypes = {
provider: PropTypes.string,
};
export const createNotSupportedComponent = message => () => {
console.error(message);
return null;
};
function getViewManagerConfig(viewManagerName) {
const UIManager = NativeModules.UIManager;
if (!UIManager.getViewManagerConfig) {
// RN < 0.58
return UIManager[viewManagerName];
}
// RN >= 0.58
return UIManager.getViewManagerConfig(viewManagerName);
}
export const googleMapIsInstalled = !!getViewManagerConfig(
getAirMapName(PROVIDER_GOOGLE)
);
export default function decorateMapComponent(
Component,
{ componentType, providers }
) {
const components = {};
const getDefaultComponent = () =>
requireNativeComponent(getAirComponentName(null, componentType), Component);
Component.contextTypes = contextTypes;
Component.prototype.getAirComponent = function getAirComponent() {
const provider = this.context.provider || PROVIDER_DEFAULT;
if (components[provider]) {
return components[provider];
}
if (provider === PROVIDER_DEFAULT) {
components[PROVIDER_DEFAULT] = getDefaultComponent();
return components[PROVIDER_DEFAULT];
}
const providerInfo = providers[provider];
const platformSupport = providerInfo[Platform.OS];
const componentName = getAirComponentName(provider, componentType);
if (platformSupport === NOT_SUPPORTED) {
components[provider] = createNotSupportedComponent(
`react-native-maps: ${componentName} is not supported on ${Platform.OS}`
);
} else if (platformSupport === SUPPORTED) {
if (
provider !== PROVIDER_GOOGLE ||
(Platform.OS === 'ios' && googleMapIsInstalled)
) {
components[provider] = requireNativeComponent(componentName, Component);
}
} else {
// (platformSupport === USES_DEFAULT_IMPLEMENTATION)
if (!components[PROVIDER_DEFAULT]) {
components[PROVIDER_DEFAULT] = getDefaultComponent();
}
components[provider] = components[PROVIDER_DEFAULT];
}
return components[provider];
};
Component.prototype.getUIManagerCommand = function getUIManagerCommand(name) {
const componentName = getAirComponentName(
this.context.provider,
componentType
);
return getViewManagerConfig(componentName).Commands[name];
};
Component.prototype.getMapManagerCommand = function getMapManagerCommand(
name
) {
const airComponentName = `${getAirComponentName(
this.context.provider,
componentType
)}Manager`;
return NativeModules[airComponentName][name];
};
return Component;
}
try adding "expoCli": "4.13.0", to your eas.json like this:
"cli": {
"version": ">= 0.41.0"
},
"build": {
"base": {
"expoCli": "4.13.0",
},
"simulator": {
"extends": "base",
"android": {
"buildType": "apk"
},
"ios": {
"simulator": true
}
},
"development": {
"extends": "base",
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"extends": "base",
"distribution": "internal"
},
"production": {
"extends": "base",
"releaseChannel": "production"
}
},
"submit": {
}
}
Had same issue, did work for me.

Quasar File Picker doesn't upload image in ios

I want to implement image uploading. It works for web (Chrome/Safari), but not for mobile ios (built with Capacitor). It shows image selection, but when I select it nothing happens.
Here is my input:
<q-file
v-model="avatarFile"
accept=".jpg, .png"
#input="uploadImage"
/>
async uploadImage() {
if (this.avatarFile) {
let extension = "jpg";
if (this.avatarFile.type === "image/png") {
extension = "png";
}
try {
this.loading = 1;
const {
data: {
uploadUrl: { uploadUrl },
},
} = await this.$apollo.query({
query: GET_UPLOAD_URL,
variables: {
extension,
},
});
await axios.put(uploadUrl, this.avatarFile);
await setTimeout(() => {
this.$emit("on-image-upload");
this.loading = 0;
}, 3000);
} catch (e) {
this.alertError(e);
this.loading = 0;
}
}
}
What am I doing wrong? Thanx in advance!
Found the solution. My accept rule had the wrong format. This works:
accept="image/*"

IOS Notification Permission alert does not show

SDK Version: 39.0.0
Platforms(Android/iOS/web/all): All
I am not getting accept or decline notifications permissions alert when loading my app in production.
I have tried clearing certificates and keys and allowing expo to add everything from a clean slate, but still no luck. I am starting to think maybe it’s my code which is the reason why the alert doesn’t get fired.
import Constants from "expo-constants";
import * as Notifications from "expo-notifications";
import { Permissions } from "expo-permissions";
import { Notifications as Notifications2 } from "expo";
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false
})
});
export default class LoginScreen extends React.Component {
state = {
email: "",
password: "",
notification: {},
errorMessage: null
};
async componentDidMount() {
this.registerForPushNotificationsAsync();
//Notifications.addNotificationReceivedListener(this._handleNotification);
Notifications2.addListener(data => {
this.setState({ notification: data });
});
Notifications.addNotificationResponseReceivedListener(
this._handleNotificationResponse
);
}
_handleNotification = notification => {
this.setState({ notification: notification });
};
_handleNotificationResponse = response => {
console.log(response);
};
handleLogin = async () => {
try {
const { email, password } = this.state;
const expoPushToken = await Notifications.getExpoPushTokenAsync();
console.log(expoPushToken);
const userinfo = await firebase
.auth()
.signInWithEmailAndPassword(email, password);
console.log("user ID ", userinfo.user.uid);
await firebase
.firestore()
.collection("users")
.doc(userinfo.user.uid.toString())
.update({
expo_token: expoPushToken["data"]
})
.then(function() {
console.log("token successfully updated!");
})
.catch(function(error) {
// The document probably doesn't exist.
console.error("Error updating document: ", error);
});
} catch (error) {
console.log("=======Error in login", error);
this.setState({ errorMessage: error.message });
}
};
registerForPushNotificationsAsync = async () => {
if (Constants.isDevice) {
const { status: existingStatus } = await Permissions.getAsync(
Permissions.NOTIFICATIONS
);
let finalStatus = existingStatus;
if (existingStatus !== "granted") {
const { status } = await Permissions.askAsync(
Permissions.NOTIFICATIONS
);
finalStatus = status;
}
if (finalStatus !== "granted") {
alert("Failed to get push token for push notification!");
return;
}
const token = await Notifications.getExpoPushTokenAsync();
console.log(token);
//this.setState({ expoPushToken: token });
} else {
alert("Must use physical device for Push Notifications");
}
if (Platform.OS === "android") {
Notifications.setNotificationChannelAsync("default", {
name: "default",
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: "#FF231F7C"
});
}
};
import { Permissions } from "expo-permissions";
should of been
import * as Permissions from 'expo-permissions';
Sometimes we all make simple mistakes.

Twilio Remote video from Safari iPhone showing black-screen in Angular 5

iPhone safari version: 11.2.5
Chrome version: Checking for updates Version 80.0.3987.163 (Official Build) (64-bit)
The connection is a peer to peer.
If I can give you an overview it's like -
Video is coming on iPhone from mac safari, Chrome, window laptop
Video is coming on Android Phone from mac safari, Chrome, window laptop
Video is coming on mac safari, Chrome, window laptop from Android Phone
Video is NOT COMING on mac safari, Chrome, window laptop from iPhone
Please let me know if you can help. Thanks in advance.
I solve this issue
createLocalVideoTrack({
video: { width: 1280, height: 720 },
}).then(track => {
if (this.localVideo) {
const element = track.attach();
this.renderer.data.id = track.sid;
this.renderer.setStyle(element, 'width', '25%');
this.renderer.appendChild(this.localVideo.nativeElement, element);
this.attachVideoClass();
}
});
Should be :
this.roomObj.localParticipant.videoTracks.forEach(publication => {
const element = publication.track.attach();
this.renderer.data.id = publication.track.sid;
this.renderer.setStyle(element, 'width', '25%');
this.renderer.appendChild(this.localVideo.nativeElement, element);
})
Twilio developer evangelist here.
I would recommend reading through the developing for Safari 11 article which goes over a bunch of potential issues.
Please check code:
import { Injectable, EventEmitter, ElementRef, Renderer2, RendererFactory2 } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs';
import { connect, createLocalTracks, createLocalVideoTrack } from 'twilio-video';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Http } from '#angular/http';
import { Router } from '#angular/router';
import { BaCustomPreLoader } from './baCustomPreloader.service';
#Injectable()
export class TwilioService {
remoteVideo: ElementRef;
localVideo: ElementRef;
previewing: boolean;
msgSubject = new BehaviorSubject("");
roomObj: any;
roomParticipants;
private renderer: Renderer2;
constructor(
private http: Http,
private router: Router,
private rendererFactory: RendererFactory2,
private baCustomPreLoader: BaCustomPreLoader) {
this.renderer = rendererFactory.createRenderer(null, null);
}
getToken(username): Observable<any> {
return this.http.post('/abc', { uid: 'ashish' });
}
microphone = true;
mute() {
this.roomObj.localParticipant.audioTracks.forEach(function (
audioTrack
) {
audioTrack.track.disable();
});
this.microphone = false;
}
unmute() {
this.roomObj.localParticipant.audioTracks.forEach(function (
audioTrack,
key,
map
) {
audioTrack.track.enable();
});
this.microphone = true;
}
connectToRoom(accessToken: string, options): void {
connect(accessToken, options).then(room => {
this.roomObj = room;
if (!this.previewing && options['video']) {
this.startLocalVideo();
this.previewing = true;
}
this.roomParticipants = room.participants;
room.participants.forEach(participant => {
this.attachParticipantTracks(participant);
});
room.on('participantDisconnected', (participant) => {
this.detachParticipantTracks(participant);
});
room.on('participantConnected', (participant) => {
this.roomParticipants = room.participants;
this.attachParticipantTracks(participant);
participant.on('trackAdded', track => {
const element = track.attach();
this.renderer.data.id = track.sid;
this.renderer.setStyle(element, 'height', '100%');
this.renderer.setStyle(element, 'max-width', '100%');
this.renderer.appendChild(this.remoteVideo.nativeElement, element);
this.attachVideoClass();
this.baCustomPreLoader.hide();
});
});
// When a Participant adds a Track, attach it to the DOM.
room.on('trackAdded', (track, participant) => {
this.attachTracks([track]);
});
// When a Participant removes a Track, detach it from the DOM.
room.on('trackRemoved', (track, participant) => {
this.detachTracks([track]);
});
room.once('disconnected', room => {
room.localParticipant.tracks.forEach(track => {
track.track.stop();
const attachedElements = track.track.detach();
attachedElements.forEach(element => element.remove());
room.localParticipant.videoTracks.forEach(video => {
const trackConst = [video][0].track;
trackConst.stop(); // <- error
trackConst.detach().forEach(element => element.remove());
room.localParticipant.unpublishTrack(trackConst);
});
let element = this.remoteVideo.nativeElement;
while (element.firstChild) {
element.removeChild(element.firstChild);
}
let localElement = this.localVideo.nativeElement;
while (localElement.firstChild) {
localElement.removeChild(localElement.firstChild);
}
this.router.navigate(['thanks']);
setTimeout(() => {
window.location.reload();
}, 1000)
});
});
}, (error) => {
alert(error.message);
});
}
attachParticipantTracks(participant): void {
participant.tracks.forEach(part => {
this.trackPublished(part);
});
}
trackPublished(publication) {
if (publication.isSubscribed)
this.attachTracks(publication.track);
if (!publication.isSubscribed)
publication.on('subscribed', track => {
this.attachTracks(track);
});
}
attachTracks(tracks) {
const element = tracks.attach();
this.renderer.data.id = tracks.sid;
this.renderer.setStyle(element, 'height', '100%');
this.renderer.setStyle(element, 'max-width', '100%');
this.renderer.appendChild(this.remoteVideo.nativeElement, element);
this.attachVideoClass();
this.baCustomPreLoader.hide();
}
startLocalVideo(): void {
createLocalVideoTrack({
video: { width: 1280, height: 720 },
}).then(track => {
if (this.localVideo) {
const element = track.attach();
this.renderer.data.id = track.sid;
this.renderer.setStyle(element, 'width', '25%');
this.renderer.appendChild(this.localVideo.nativeElement, element);
this.attachVideoClass();
}
});
}
detachParticipantTracks(participant) {
this.detachTracks(participant);
}
detachTracks(tracks): void {
tracks.tracks.forEach(track => {
let element = this.remoteVideo.nativeElement;
while (element.firstChild) {
element.removeChild(element.firstChild);
}
});
}
attachVideoClass() {
let remote = document.getElementById('remote');
}
}

Resources