I'm trying to load a mp4 video stream from Django to Vue.js. I followed the solution here and got a byte string via my axios API which I concatenated to data:video/mp4;base64,, and then binded it to the video tag's :src property, but the video is not displaying. Is this the correct way to stream videos from Django 3 to Vue.js? What am I doing wrong?
Api.js code snippet:
import axios from 'axios'
export default () => {
return axios.create({
baseURL: `http://127.0.0.1:8000/`,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
})
}
Video.vue component code snippet:
methods: {
async stream() {
return await VideoService.stream(this.video)
.then(function(data) {
return data.data;
});
}
},
props: ['data'],
watch: {
data: {
deep: true,
immediate: true,
handler(curr, prev) {
this.video = curr;
this.stream().then(data => {
data = Buffer.from(data, 'binary').toString('base64');
this.video = 'data:video/mp4;base64,'+data;
});
}
}
}
As suggested in the comments of that answer, I also set src to the Django api endpoint e.g. src="/video/test.mp4, but I can see in my terminal that it's not reaching that Django route. How do I get the linked solution to work with Vue.js?
Picture of the raw string I get back from Kevin's Django view:
Another image when I convert to base 64 using Buffer:
The final solution needed to stream videos to Vue.js if you're using the same component for videos is to set the src tag using v-html so that you can set src dynamically. Directly doing src="http://localhost:8000/v/video.mp4" won't work once the component is created. So in addition to Kevin's code, just do the following in the video component:
<template>
<div>
<video v-html="src" autoplay="" controls="" loop="" muted="" frameborder="0">
Your browser does not support HTML videos.
</video>
</div>
</template>
<script>
export default {
data() {
return {
src: ''
}
},
props: ['data'],
watch: {
data: {
deep: true,
immediate: true,
handler(curr, prev) {
this.src = '<source src="http://localhost:8000/v/"'+curr+' type="video/mp4">'
}
}
}
}
</script>
<style scoped>
</style>
Related
I am trying to pass data from the server to the client to load my app faster and prevent multiple calls to the database.
Via Fetch
SvelteKit is made to do this via the fetch function. This is great if you have an endpoint that allows for custom fetch. But what if you don't?
Firebase is a perfect example of not having a custom fetch function.
Cookies
I would think I could use cookies, but when I set the cookie, it just prints 'undefined' and never gets set.
<script lang="ts" context="module">
import Cookies from 'js-cookie';
import { browser } from '$app/env';
import { getResources } from '../modules/resource';
export async function load() {
if (browser) {
// working code would use JSON.parse
const c = Cookies.get('r');
return {
props: {
resources: c
}
};
} else {
// server
const r = await getResources();
// working code would use JSON.stringify
Cookies.set('resources', r);
// no cookies were set?
console.log(Cookies.get());
return {
props: {
resources: r
}
};
}
}
</script>
So my code loads correctly, then dissapears when the browser load function is loaded...
Surely there is a functioning way to do this?
J
So it seems the official answer by Rich Harris is to use and a rest api endpoint AND fetch.
routes/something.ts
import { getFirebaseDoc } from "../modules/posts";
export async function get() {
return {
body: await getFirebaseDoc()
};
}
routes/content.svelte
export async function load({ fetch }) {
const res = await fetch('/resources');
if (res.ok) {
return {
props: { resources: await res.json() }
};
}
return {
status: res.status,
error: new Error()
};
}
This seems extraneous and problematic as I speak of here, but it also seems like the only way.
J
You need to use a handler that injects the cookie into the server response (because load functions do not expose the request or headers to the browser, they are just used for loading props I believe). Example here: https://github.com/sveltejs/kit/blob/59358960ff2c32d714c47957a2350f459b9ccba8/packages/kit/test/apps/basics/src/hooks.js#L42
https://kit.svelte.dev/docs/hooks#handle
export async function handle({ event, resolve }) {
event.locals.user = await getUserInformation(event.request.headers.get('cookie'));
const response = await resolve(event);
response.headers.set('x-custom-header', 'potato');
response.headers.append('set-cookie', 'name=SvelteKit; path=/; HttpOnly');
return response;
}
FYI: This functionality was only added 11 days ago in #sveltejs/kit#1.0.0-next.267: https://github.com/sveltejs/kit/pull/3631
No need to use fetch!
You can get the data however you like!
<script context="module">
import db from '$/firebaseConfig'
export async function load() {
const eventref = db.ref('cats/whiskers');
const snapshot = await eventref.once('value');
const res = snapshot.val();
return { props: { myData: res.data } } // return data under `props` key will be passed to component
}
</script>
<script>
export let myData //data gets injected into your component
</script>
<pre>{JSON.stringify(myData, null, 4)}</pre>
Here's a quick demo on how to fetch data using axios, same principle applies for firebase: https://stackblitz.com/edit/sveltejs-kit-template-default-bpr1uq?file=src/routes/index.svelte
If you want to only load data on the server you should use an "endpoint" (https://kit.svelte.dev/docs/routing#endpoints)
My solution might solve it especially for those who work with (e.g: laravel_session), actually in your case if you want to retain the cookie data when loading on each endpoint.
What you should gonna do is to create an interface to pass the event on every api() call
interface ApiParams {
method: string;
event: RequestEvent<Record<string, string>>;
resource?: string;
data?: Record<string, unknown>;
}
Now we need to modify the default sveltekit api(), provide the whole event.
// localhost:3000/users
export const get: RequestHandler = async (event) => {
const response = await api({method: 'get', resource: 'users', event});
// ...
});
Inside your api() function, set your event.locals but make sure to update your app.d.ts
// app.d.ts
declare namespace App {
interface Locals {
r: string;
}
//...
}
// api.ts
export async function api(params: ApiParams) {
// ...
params.event.locals.r = response.headers.get('r')
});
Lastly, update your hooks.ts
/** #type {import('#sveltejs/kit').Handle} */
export const handle: Handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
const response = await resolve(event);
if (!cookies.whatevercookie && event.locals.r) {
response.headers.set(
'set-cookie',
cookie.serialize('whatevercookie', event.locals.r, {
path: '/',
httpOnly: true
})
);
}
return response;
});
Refer to my project:
hooks.ts
app.d.ts
_api.ts
index.ts
I have a problem with updating data using vue js as the frontend and django as the backend
when updating the data with the data image is changed successfully. but when updating data without an image with an error.
i've fire test using postman and managed to update data without changing image. please find a solution
this is the method for updating the data
ubah() {
let datapengajarstaf = new FormData();
datapengajarstaf.append("nama", this.datapengajarstaf.nama);
datapengajarstaf.append("nip", this.datapengajarstaf.nip);
datapengajarstaf.append("jobs", this.datapengajarstaf.jobs);
datapengajarstaf.append("picture", this.gambar);
_.each(this.datapengajarstaf, (value, key) => {
datapengajarstaf.append(key, value);
});
axios
.put("http://127.0.0.1:8000/api/Detailpengajarstaff/"+this.id, datapengajarstaf,
{
headers: {
"Content-Type":"multipart/form-data"
}
}
)
.then(response => {
this.$router.push("/indexpengajarstaff");
this.$q.notify({
type: "positive",
message: `Data berhasil ditambah.`
});
})
.catch(err => {
if (err.response.status === 422) {
this.errors = [];
_.each(err.response.data.errors, error => {
_.each(error, e => {
this.errors.push(e);
});
});
}
});
}
this is for form-data when the data is updated
I'm using react-native-image-picker and react-native-s3-upload to upload images to s3 bucket.
RNS3.put(file, AWSOptions)
.then((response) => {
if (response.status !== 201) {
throw new Error('Failed to upload image to S3');
}
})
.catch(function (error) {
console.log('Error', JSON.stringify(error));
throw error;
});
and I get a response like this without image been uploaded.
Object {
"headers": Object {},
"status": 0,
"text": "Stream Closed",
}
I have exactly the same issue with a similar library called react-native-aws3.
It seems related to the new React Native version 0.62.2, because I've tried to upgrade today (my previous version was 0.61.5) and that library worked before.
I don't know if both libraries use a common API which now is broken in the new RN, but I suggest you to downgrade in the meanwhile.
I had the same issue and tried for days to fix it. This is what I came up with.
Make sure you follow the amplify guide and set up amplify init, amplify add auth and then do amplify push
Then do amplify add storage and follow the guide here (https://docs.amplify.aws/lib/storage/upload/q/platform/js). Then copy this code.
import Amplify, { Storage } from 'aws-amplify'
import config from './src/aws-exports'
// import awsconfig from './aws-exports';
// Might need to switch line 7 to awsconfig
Amplify.configure(config)
import { StatusBar } from 'expo-status-bar';
import React, { useState, useEffect } from 'react';
import { Button, Image, View, Platform, StyleSheet, Text } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
function App() {
const [image, setImage] = useState(null)
useEffect(() => {
(async () => {
if (Platform.OS !== 'web') {
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
alert('Sorry, we need camera roll permissions to make this work!');
}
}
})();
}, []);
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
console.log(result)
async function pathToImageFile(data) {
try {
const response = await fetch(data);
const blob = await response.blob();
await Storage.put(`customer/images`, blob, {
contentType: 'image/jpeg', // contentType is optional
});
} catch (err) {
console.log('Error uploading file:', err);
}
}
// later
pathToImageFile(result.uri);
}
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Pick an image from camera roll" onPress={pickImage} />
{image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
<Button title="Upload image" onPress={() => {alert(image)}} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
export default withAuthenticator(App)```
I found an alternative module (aws-sdk) to upload files to the S3 bucket with RN 62 version.
Also, I have used base64-arraybuffer and react-native-fs modules well.
const s3bucket = new S3({
accessKeyId: XXXXXXXXXX,
secretAccessKey: XXXXXXXXXXXX,
Bucket: XXXXXXXXX,
signatureVersion: 'v4',
});
let contentType = 'image/jpeg';
let contentDeposition = 'inline;filename="' + file.name + '"';
const base64 = await fs.readFile(file.uri, 'base64');
const arrayBuffer = decode(base64);
s3bucket.createBucket(() => {
const params = {
Bucket: XXXXXXXXXXX,
Key: file.name,
Body: arrayBuffer,
ContentDisposition: contentDeposition,
ContentType: contentType,
};
s3bucket.upload(params, (err, data) => {
if (err) {
console.log('error in callback');
console.log(err);
setSpinner(false);
}
console.log('success');
console.log(data.Location);
setImageUrl(data.Location);
setSpinner(false);
});
});
The problem is with RN >= 0.62
You can either roll back to version < 0.62 or you can follow what I did.
I solved it by commenting out line number 43 in android/app/src/debug/java/**/ReactNativeFlipper.java
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
#Override
public void apply(OkHttpClient.Builder builder) {
// builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); <---- This line
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
You will need to rebuild the app after doing this.
Hope this helps.
AWS S3 upload is a powerful service provided by React Native AWS amplify. The issue here might be caused by the React Native version or improper configuration of the package. You can check some amazing articles out there that will definitely help you integrate React Native AWS amplify properly based on your project configurations and make the AWS S3 upload working properly as well.
I want to send a POST request containing JSON to a server for processing. The server will then return a PDF which I want to receive, save to the filesystem and display using nativescript-pdf-view. However, File.writeSync() will not accept the ArrayBuffer that I get from the server response. Instead, it expects the native variants NSData (iOS) and ByteArray (Android). How can I convert the ArrayBuffer to the native byte arrays?
Sending the POST request
As explained by the official documentation, a POST request can be sent using the getBinary() function:
import { getBinary } from 'tns-core-modules/http'
getBinary({
url: 'https://example.com/foo/bar/generate-pdf',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
content: JSON.stringify(someObject),
})
Here, someObject is the object you want to convert to JSON and send to the server.
Receiving, converting and saving the file / PDF
The response will contain the PDF or any other file as an ArrayBuffer. As mentioned in the question, the File.writeSync() method expects NSData (iOS) and ByteArray (Android). So, we need to get a file handler, convert our file and save it to the filesystem.
import { getBinary } from 'tns-core-modules/http'
import { isAndroid } from 'tns-core-modules/platform'
import { File, knownFolders, path } from 'tns-core-modules/file-system'
getBinary({
url: 'https://example.com/foo/bar/generate-pdf',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
content: JSON.stringify(someObject),
}).then((buffer) => {
// Get file handler
const documentsFolder = knownFolders.documents()
const destination = path.join(documentsFolder.path, 'result.pdf')
const file = File.fromPath(destination)
// Convert buffer to byte array
let byteArray
if (isAndroid) {
byteArray = Array.create('byte', buffer.byteLength)
for (let i = 0; i < buffer.length; i++) {
byteArray[i] = new java.lang.Byte(buffer[i])
}
} else {
byteArray = NSData.dataWithBytesLength(buffer, buffer.byteLength)
}
// Save file
file.writeSync(byteArray, (error) => {
console.log(error)
});
})
Why the File.writeSync() method doesn't convert this automatically is a miracle to me. Maybe someone else can explain!
Displaying the PDF
To display the PDF, you can use nativescript-pdf-view. Just use the filehandler to get the path file.path or the destination variable and pass it to the src property of the PDFView component. This might look like this:
<template>
<Page>
<ActionBar title="PDF" />
<GridLayout rows="*" columns="*">
<PDFView :src="path" row="0" col="0" />
</GridLayout>
</Page>
</template>
<script lang="ts">
export default {
name: 'PdfViewer',
props: {
path: {
type: String,
required: true,
},
},
}
</script>
Make sure to install the plugin, of course and introduce the element to Vue.js, first:
// main.ts
Vue.registerElement('PDFView', () => require('nativescript-pdf-view').PDFView)
I am working on an app which was created with the Vue loader's webpack template.
I included testing with Karma as an option when creating the project, so it was all set up and I haven't changed any of the config.
The app is a Github user lookup which currently consists of three components; App.vue, Stats.vue and UserForm.vue. The stats and form components are children of the containing app component.
Here is App.vue:
<template>
<div id="app">
<user-form
v-model="inputValue"
#go="submit"
:input-value="inputValue"
></user-form>
<stats
:username="username"
:avatar="avatar"
:fave-lang="faveLang"
:followers="followers"
></stats>
</div>
</template>
<script>
import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import _ from 'lodash'
import UserForm from './components/UserForm'
import Stats from './components/Stats'
Vue.use(VueAxios, axios)
export default {
name: 'app',
components: {
UserForm,
Stats
},
data () {
return {
inputValue: '',
username: '',
avatar: '',
followers: [],
faveLang: '',
urlBase: 'https://api.github.com/users'
}
},
methods: {
submit () {
if (this.inputValue) {
const api = `${this.urlBase}/${this.inputValue}`
this.fetchUser(api)
}
},
fetchUser (api) {
Vue.axios.get(api).then((response) => {
const { data } = response
this.inputValue = ''
this.username = data.login
this.avatar = data.avatar_url
this.fetchFollowers()
this.fetchFaveLang()
}).catch(error => {
console.warn('ERROR:', error)
})
},
fetchFollowers () {
Vue.axios.get(`${this.urlBase}/${this.username}/followers`).then(followersResponse => {
this.followers = followersResponse.data.map(follower => {
return follower.login
})
})
},
fetchFaveLang () {
Vue.axios.get(`${this.urlBase}/${this.username}/repos`).then(reposResponse => {
const langs = reposResponse.data.map(repo => {
return repo.language
})
// Get most commonly occurring string from array
const faveLang = _.chain(langs).countBy().toPairs().maxBy(_.last).head().value()
if (faveLang !== 'null') {
this.faveLang = faveLang
} else {
this.faveLang = ''
}
})
}
}
}
</script>
<style lang="stylus">
body
background-color goldenrod
</style>
Here is Stats.vue:
<template>
<div class="container">
<h1 class="username" v-if="username">{{username}}</h1>
<img v-if="avatar" :src="avatar" class="avatar">
<h2 v-if="faveLang">Favourite Language: {{faveLang}}</h2>
<h3 v-if="followers.length > 0">Followers ({{followers.length}}):</h3>
<ul v-if="followers.length > 0">
<li v-for="follower in followers">
{{follower}}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'stats',
props: [
'username',
'avatar',
'faveLang',
'followers'
]
}
</script>
<style lang="stylus" scoped>
h1
font-size 44px
.avatar
height 200px
width 200px
border-radius 10%
.container
display flex
align-items center
flex-flow column
font-family Comic Sans MS
</style>
And here is UserForm.vue:
<template>
<form #submit.prevent="handleSubmit">
<input
class="input"
:value="inputValue"
#input="updateValue($event.target.value)"
type="text"
placeholder="Enter a GitHub username..."
>
<button class="button">Go!</button>
</form>
</template>
<script>
export default {
props: ['inputValue'],
name: 'user-form',
methods: {
updateValue (value) {
this.$emit('input', value)
},
handleSubmit () {
this.$emit('go')
}
}
}
</script>
<style lang="stylus" scoped>
input
width 320px
input,
button
font-size 25px
form
display flex
justify-content center
</style>
I wrote a trivial test for UserForm.vue which test's the outerHTML of the <button>:
import Vue from 'vue'
import UserForm from 'src/components/UserForm'
describe('UserForm.vue', () => {
it('should have a data-attribute in the button outerHTML', () => {
const vm = new Vue({
el: document.createElement('div'),
render: (h) => h(UserForm)
})
expect(vm.$el.querySelector('.button').outerHTML)
.to.include('data-v')
})
})
This works fine; the output when running npm run unit is:
UserForm.vue
✓ should have a data-attribute in the button outerHTML
However, when I tried to write a similarly simple test for Stats.vue based on the documentation, I ran into a problem.
Here is the test:
import Vue from 'vue'
import Stats from 'src/components/Stats'
// Inspect the generated HTML after a state update
it('updates the rendered message when vm.message updates', done => {
const vm = new Vue(Stats).$mount()
vm.username = 'foo'
// wait a "tick" after state change before asserting DOM updates
Vue.nextTick(() => {
expect(vm.$el.querySelector('.username').textContent).toBe('foo')
done()
})
})
and here is the respective error when running npm run unit:
ERROR LOG: '[Vue warn]: Error when rendering root instance: '
✗ updates the rendered message when vm.message updates
undefined is not an object (evaluating '_vm.followers.length')
I have tried the following in an attempt to get the test working:
Change how the vm is created in the Stats test to be the same as the UserForm test - same error is returned
Test individual parts of the component, for example the textContent of a div in the component - same error is returned
Why is the error referring to _vm.followers.length? What is _vm with an underscore in front? How can I get around this issue to be able to successfully test my component?
(Repo with all code: https://github.com/alanbuchanan/vue-github-lookup-2)
Why is the error referring to _vm.followers.length? What is _vm with an underscore in front?
This piece of code is from the render function that Vue compiled your template into. _vm is a placeholder that gets inserted automatically into all Javascript expressions when vue-loader converts the template into a render function during build - it does that to provide access to the component.
When you do this in your template:
{{followers.length}}
The compiled result in the render function for this piece of code will be:
_vm.followers.length
Now, why does the error happen in the first place? Because you have defined a prop followers on your component, but don't provide any data for it - therefore, the prop's value is undefined
Solution: either you provide a default value for the prop:
// Stats.vue
props: {
followers: { default: () => [] }, // function required to return fresh object
// ... other props
}
Or you propvide acual values for the prop:
// in the test:
const vm = new Vue({
...Stats,
propsData: {
followers: [/* ... actual data*/]
}
}).$mount()