I'm using videojs in a vue application to play a pre-signed URL for a mp4 video file on S3. I'm using the pre-signed URL as the source of the videojs player but I get the error
The media could not be loaded, either because the server or network
failed or because the format is not supported.
This is how my URL looks like:
https://bucket-name.s3.region.amazonaws.com/object.mp4?AWSAccessKeyId=xxxxxxxxxxxx&Expires=xxxxxxx&Signature=xxxxxx&x-amz-security-token=xxxxxxx
I looked at similar questions on SO and someone suggested to change the URL format to below format, but that didn't work either.
https://s3.amazonaws.com/bucket-name/object.mp4?AWSAccessKeyId=xxxxxxxxxxxx&Expires=xxxxxxx&Signature=xxxxxx&x-amz-security-token=xxxxxxx
The video player plays the video if I put either of the above hardcoded URLs as its source, but not when I do as variables.
Why is it changing behaviour when a variable is used ?
<template>
<div>
<video-player
:options="{
autoplay: false,
controls: true,
sources: [
{
src: `${URL}`,
type: 'video/mp4',
},
],
}"
/>
</div>
</template>
<script>
import VideoPlayer from '#/components/VideoPlayer.vue';
var AWS = require('aws-sdk');
var bucketName = 'xxxx';
var s3 = new AWS.S3({
apiVersion: '2006-03-01',
params: { Bucket: bucketName },
});
export default {
components: {
VideoPlayer,
},
data() {
return {
URL: String,
};
},
methods: {
getURL() {
var self = this;
let myPromise = new Promise(function (myResolve, myReject) {
const url = s3.getSignedUrl('getObject', {
Key: `xxxxx.mp4`,
Expires: 3600,
});
myResolve(url);
myReject('sorry, error');
});
myPromise.then(
function (value) {
self.URL = value;
},
function (error) {
console.log(error);
}
);
},
},
mounted() {
this.getURL();
},
};
</script>
The issue was that the URL value was not being set as the source, as mentioned by #misterben.
I made the following changes to set the source in the method. Although there might be better ways of setting the source in Vue ( other than using querySelector)
<video-player
:options="{
autoplay: false,
controls: true,
}"
/>
</div>
and,
import videojs from 'video.js';
getSignedUrl() {
// this is because the imported videoPlayer component has class="video-js"
var player = videojs(document.querySelector('.video-js'));
var self = this;
var params = {
Bucket: 'xxxxx',
Key: `xxxxx`,
};
var promise = s3.getSignedUrlPromise('getObject', params);
promise.then(
async function (url) {
self.URL = url;
await player.src({
src: url,
type: 'video/mp4' /*video type*/,
});
},
function (err) {
console.log(err);
}
);
},
Related
this is my first discussion post here. I have learned Apollo + GraphQL through Odyssey. Currently, I am building my own project using Next.js which required fetching data from 2 GraphQL endpoints.
My problem: How can I fetch data from multiple GraphQL endpoints with ApolloClient?
Below is my code for my first endpoint:
import { ApolloClient, InMemoryCache, createHttpLink } from "#apollo/client";
const client = new ApolloClient({
ssrMode: true,
link: createHttpLink({
uri: "https://api.hashnode.com/",
credentials: "same-origin",
headers: {
Authorization: process.env.HASHNODE_AUTH,
},
}),
cache: new InMemoryCache(),
});
export default client;
What you are trying to accomplish is kinda against Apollo's "One Graph" approach.
Take a look at gateways and federation - https://www.apollographql.com/docs/federation/
With that being said, some hacky solution is possible but you will need to maintain a more complex structure and specify the endpoint in every query, which undermines the built-in mechanism and might cause optimization issues.
//Declare your endpoints
const endpoint1 = new HttpLink({
uri: 'https://api.hashnode.com/graphql',
...
})
const endpoint2 = new HttpLink({
uri: 'endpoint2/graphql',
...
})
//pass them to apollo-client config
const client = new ApolloClient({
link: ApolloLink.split(
operation => operation.getContext().clientName === 'endpoint2',
endpoint2, //if above
endpoint1
)
...
})
//pass client name in query/mutation
useQuery(QUERY, {variables, context: {clientName: 'endpoint2'}})
This package seems to do what you want: https://github.com/habx/apollo-multi-endpoint-link
Also, check the discussion here: https://github.com/apollographql/apollo-client/issues/84
Encountered the same problem today. I wanted to have it dynamic so this is what I came out with:
export type DynamicLinkClientName = "aApp" | "bApp" | "graphqlApp";
type Link = RestLink | HttpLink;
type DynamicLink = { link: Link; name: DynamicLinkClientName };
const LINK_MAP: DynamicLink[] = [
{ link: aRestLink, name: "aApp" },
{ link: bAppRestLink, name: "bApp" },
{ link: graphqlAppLink, name: "graphqlApp" },
];
const isClientFromContext = (client: string) => (op: Operation) =>
op.getContext().client === client;
const DynamicApolloLink = LINK_MAP.reduce<ApolloLink | undefined>(
(prevLink, nextLink) => {
// When no name is specified, fallback to defaultLink.
if (!prevLink) {
return ApolloLink.split(
isClientFromContext(nextLink.name),
nextLink.link,
defaultLink
);
}
return ApolloLink.split(
isClientFromContext(nextLink.name),
nextLink.link,
prevLink
);
},
undefined
) as ApolloLink;
I have a project where it uses Filepond to upload files and I need it to load file from server.
I already follow the docs but It doesn't work. The Filepond gives error Error during load 400 and it even doesn't send the request to load the file from server
This is my javascript
let pond = FilePond.create(value, {
files: [
{
// the server file reference
source: 'e958818e-92de-4953-960a-d8157467b766',
// set type to local to indicate an already uploaded file
options: {
type: 'local'
}
}
]
});
FilePond.setOptions({
labelFileProcessingError: (error) => {
return error.body;
},
server: {
headers: {
'#tokenSet.HeaderName' : '#tokenSet.RequestToken'
},
url: window.location.origin,
process: (fieldName, file, metadata, load, error, progress, abort) => {
// We ignore the metadata property and only send the file
fieldName = "File";
const formData = new FormData();
formData.append(fieldName, file, file.name);
const request = new XMLHttpRequest();
request.open('POST', '/UploadFileTemp/Process');
request.setRequestHeader('#tokenSet.HeaderName', '#tokenSet.RequestToken');
request.upload.onprogress = (e) => {
progress(e.lengthComputable, e.loaded, e.total);
};
request.onload = function () {
if (request.status >= 200 && request.status < 300) {
load(request.responseText);
}
else {
let errorMessageFromServer = request.responseText;
error('oh no');
}
};
request.send(formData);
},
revert: "/UploadFileTemp/revert/",
load: "/UploadFileTemp/load"
}
})
This is my controller
public async Task<IActionResult> Load(string p_fileId)
{
//Code to get the files
//Return the file
Response.Headers.Add("Content-Disposition", cd.ToString());
Response.Headers.Add("X-Content-Type-Options", "nosniff");
return PhysicalFile(filePath, "text/plain");
}
NB
I already test my controller via postman and it works. I also check the content-disposition header
I'd advise to first set all the options and then set the files property.
You're setting the files, and then you're telling FilePond where to find them, it's probably already trying to load them but doesn't have an endpoint (yet).
Restructuring the code to look like this should do the trick.
let pond = FilePond.create(value, {
server: {
headers: {
'#tokenSet.HeaderName': '#tokenSet.RequestToken',
},
url: window.location.origin,
process: (fieldName, file, metadata, load, error, progress, abort) => {
// your processing method
},
revert: '/UploadFileTemp/revert',
load: '/UploadFileTemp/load',
},
files: [
{
// the server file reference
source: 'e958818e-92de-4953-960a-d8157467b766',
// set type to local to indicate an already uploaded file
options: {
type: 'local',
},
},
],
});
I am working on a media player right now but I am keen to find a solution where I can preload first 4 audio files and when I am going to next one the function with preload will run again. I am using an array with url path for each that points to s3 bucket not locally. I also want to mention that I am using expo-av package
For now the loading looks like this
componentDidMount() {
Audio.setAudioModeAsync({
allowsRecordingIOS: false,
staysActiveInBackground: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS: true,
shouldDuckAndroid: true,
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
playThroughEarpieceAndroid: false
});
}
async _loadNewPlaybackInstance(playing) {
if (this.playbackInstance != null) {
await this.playbackInstance.unloadAsync();
this.playbackInstance = null;
}
const source = { uri: PLAYLIST[this.index].url };
const initialStatus = {
shouldPlay: playing,
rate: this.state.rate,
shouldCorrectPitch: this.state.shouldCorrectPitch,
volume: this.state.volume,
};
const { sound, status } = await Audio.Sound.createAsync(
source,
initialStatus,
this._onPlaybackStatusUpdate
);
this.playbackInstance = sound;
if (status) {
this._onPlayPausePressed()
}
this._updateScreenForLoading(false);
}
I am using Vue.js in the front-end. I have Node.js, Express, PostgreSQL (with Sequelize ) on the backend.
I am storing an item in the database that includes a thumbnail image.
Database Model
const Item = sequelize.define('item', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.TEXT,
allowNull: false,
},
image: {
type: Sequelize.BLOB('long'),
allowNull: true,
},
Database-wise, the image is being stored as a Blob and I think this is fine (and yes, I am aware that it's not best-practice to put images in a database).
I observe in the browser that the object that I am accessing in my Vue template with this.item.image is an Object of the type Buffer.
Adding to Database
I add the item to the database in the browser with this in my vue template:
<label for="image" class="itemCreate__field itemCreate__field--image">
<span class="itemCreate__fieldLabel">Image</span>
<input id="file" type="file" accept="image/*" #change="onFileChange"/>
<img v-if="itemPreviewImage" :src="itemPreviewImage" />
</label>
And that HTML relies on these methods:
onFileChange(evt) {
const files = evt.target.files || evt.dataTransfer.files;
if (!files.length) return;
this.createImage(files[0]);
},
createImage(file) {
const image = new Image();
const reader = new FileReader();
reader.onload = evt => {
this.itemPreviewImage = evt.target.result;
this.item.image = evt.target.result;
}
reader.readAsDataURL(file);
},
I have this in the vue template that renders the image:
<div v-if="item.image">
<img :src="imgUrl" alt="Picture of item"/>
</div>
Rendering from Database
I have tried the following approaches, which do not work:
createObjectUrl borrowed from here:
imgUrl(){
const objUrl = window.URL.createObjectURL(new Blob(this.item.image.data));
return objUrl;
}
Creating a base64 string borrowed from here:
imgUrl(){
const intArray = new Uint8Array(this.item.image.data);
const reducedArray = intArray.reduce((data, byte) => data + String.fromCharCode(byte), '');
const base64String = `data:image/png;base64, ${btoa(reducedArray)}`;
return base64String;
}
Creating a new Uint8Array and then getting an objectUrl (borrowed here):
imgUrl(){
const arrayBuffer = new Uint8Array(this.item.image);
const blob = new Blob([arrayBuffer], {type: "image/png"});
return window.URL.createObjectURL(blob);
}
In all cases (including some attempts with FileReader), I get broken images. I don't get errors in the console, though.
I think the issue is that I am not submitting correct data to the database.
I am sending an Ajax request that has the File attached as a property, and I should probably convert it to ¿something else?
First, be sure you're getting a valid base64 string: https://codebeautify.org/base64-to-image-converter
Then try defining a getter to the Item model
const Item = sequelize.define('item', {
...
image: {
type: Sequelize.BLOB('long'),
allowNull: true,
get () { // define a getter
const data = this.getDataValue('image')
return data ? data.toString('base64') : ''
},
set(val) {
this.setDataValue('image', val);
}
},
...
}
Computed property
imgURL () {
return this.item.image
? 'data:image/png;charset=utf-8;base64,' + this.item.image
: '' // some default image
}
I have downloaded this library into my project and put it into "lib" folder in my project.
Then I add it into the cotroller of my view, when I want to call it when clicking the button, as described in the documentation
sap.ui.define([
"sap/ui/core/mvc/Controller",
"Test_ScreenRecordingTest_ScreenRecording/lib/RecordRTC"
], function(Controller, RecordRTC) {
"use strict";
return Controller.extend("Test_ScreenRecordingTest_ScreenRecording.controller.View1", {
onStartRecording: function(){
debugger;
var mediaConstraints = { video: true, audio: true };
navigator.mediaDevices.getUserMedia(mediaConstraints).then(this.successCallback.bind(this)).catch(this.errorCallback);
},
successCallback: function(stream) {
// RecordRTC usage goes here
var options = {
mimeType: 'video/webm', // or video/webm\;codecs=h264 or video/webm\;codecs=vp9
audioBitsPerSecond: 128000,
videoBitsPerSecond: 128000,
bitsPerSecond: 128000 // if this line is provided, skip above two
};
//jQuery.sap.require("Test_ScreenRecordingTest_ScreenRecording.lib.RecordRTC");
this.recordRTC = RecordRTC(stream, options);
this.recordRTC.startRecording();
},
errorCallback: function(error) {
console.log(error)
debugger;
},
onStopRecording: function(){
this.recordRTC.stopRecording(function (audioVideoWebMURL) {
video.src = audioVideoWebMURL;
var recordedBlob = this.recordRTC.getBlob();
debugger;
this.recordRTC.getDataURL(function(dataURL) {
debugger;
});
});
}
});
If I don't use the RecordRTC variable, I can see it in the debugger. If I use it, it appears as "undefined". So can never call it.
Could you please help??Ç
EDIT 09-feb-2018: Solved declaring a new variable in the Controller extension
return Controller.extend("Test_ScreenRecordingTest_ScreenRecording.controller.View1", {
//this line solved the issue
RecordRTC: RecordRTC,
onStartRecording: function(){
debugger;
var mediaConstraints = { video: true, audio: true };
navigator.mediaDevices.getUserMedia(mediaConstraints).then(this.successCallback.bind(this)).catch(this.errorCallback);
},
Thank you in advance
The dependency string in your code looks strange:
"Test_ScreenRecordingTest_ScreenRecording/lib/RecordRTC".
Can it be a typo?
Anyway, the dependency path should be like this: "<app ID from manifest.json>/lib/RecordRTC".