Browse Source

Remote access: add the stream in the browse screen

transifex
Nicolas Pomepuy 3 years ago
parent
commit
0c745c4d3c
  1. 26
      application/resources/src/main/res/drawable/ic_remote_stream_add.xml
  2. 19
      application/webserver/src/main/java/org/videolan/vlc/webserver/RemoteAccessRouting.kt
  3. 2
      application/webserver/src/main/java/org/videolan/vlc/webserver/TranslationMapping.kt
  4. 3
      buildsystem/network-sharing-server/src/App.vue
  5. 76
      buildsystem/network-sharing-server/src/components/AddStream.vue
  6. 2
      buildsystem/network-sharing-server/src/components/MediaCardItem.vue
  7. 2
      buildsystem/network-sharing-server/src/components/MediaListItem.vue
  8. 54
      buildsystem/network-sharing-server/src/pages/BrowseList.vue
  9. 4
      buildsystem/network-sharing-server/src/plugins/api.js
  10. 3
      buildsystem/network-sharing-server/src/plugins/vlcUtils.js
  11. 1
      buildsystem/network-sharing-server/src/stores/AppStore.js

26
application/resources/src/main/res/drawable/ic_remote_stream_add.xml

@ -0,0 +1,26 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillAlpha="1"
android:fillColor="@color/orange800"
android:fillType="nonZero"
android:pathData="M11,7h2v10h-2z"
android:strokeWidth="0.49999979"
android:strokeAlpha="1"
android:strokeColor="#00000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillAlpha="1"
android:fillColor="@color/orange800"
android:fillType="nonZero"
android:pathData="M7,11h10v2h-10z"
android:strokeWidth="0.49999979"
android:strokeAlpha="1"
android:strokeColor="#00000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

19
application/webserver/src/main/java/org/videolan/vlc/webserver/RemoteAccessRouting.kt

@ -561,6 +561,19 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
val gson = Gson()
call.respondText(gson.toJson(list))
}
get("/stream-list") {
verifyLogin(settings)
val stream = appContext.getFromMl {
lastStreamsPlayed()
}
val list = ArrayList<RemoteAccessServer.PlayQueueItem>()
stream.forEachIndexed { index, mediaLibraryItem ->
list.add(RemoteAccessServer.PlayQueueItem(3000L + index, mediaLibraryItem.title, "", 0, mediaLibraryItem.artworkMrl
?: "", false, "", (mediaLibraryItem as MediaWrapper).uri.toString(), true))
}
val gson = Gson()
call.respondText(gson.toJson(list))
}
//list of folders and files in a path
get("/browse-list") {
verifyLogin(settings)
@ -734,6 +747,12 @@ fun Route.setupRouting(appContext: Context, scope: CoroutineScope) {
return@get
}
}
if (call.request.queryParameters["type"] == "new-stream") {
BitmapUtil.encodeImage(BitmapUtil.vectorToBitmap(appContext, R.drawable.ic_remote_stream_add, 256, 256), true)?.let {
call.respondBytes(ContentType.Image.PNG) { it }
return@get
}
}
if (call.request.queryParameters["type"] == "file") {
BitmapUtil.encodeImage(BitmapUtil.vectorToBitmap(appContext, R.drawable.ic_browser_unknown_normal, 256, 256), true)?.let {
call.respondBytes(ContentType.Image.PNG) { it }

2
application/webserver/src/main/java/org/videolan/vlc/webserver/TranslationMapping.kt

@ -85,5 +85,7 @@ object TranslationMapping {
NOTHING_RESUME(R.string.resume_playback_error),
INVALID_LOGIN(R.string.ra_invalid_login),
INVALID_OTP(R.string.ra_invalid_otp),
NEW_STREAM(R.string.new_stream),
ENTER_STREAM(R.string.open_mrl_dialog_msg),
}
}

3
buildsystem/network-sharing-server/src/App.vue

@ -6,6 +6,7 @@
<PlayQueue />
<MiniPlayer ref="miniPlayer" />
<Alert />
<AddStream />
<UploadDialog ref="uploadComponent" />
<div class="main-loading" v-show="this.appStore.loading">
<div class="spinner-border text-primary" role="status">
@ -19,6 +20,7 @@ import AppHeader from './components/AppHeader.vue'
import MiniPlayer from './components/MiniPlayer.vue'
import PlayQueue from './components/PlayQueue.vue'
import Alert from './components/Alert.vue'
import AddStream from './components/AddStream.vue'
import UploadDialog from './components/UploadDialog.vue'
import { vlcApi } from './plugins/api.js'
import { usePlayerStore } from './stores/PlayerStore'
@ -33,6 +35,7 @@ export default {
MiniPlayer,
PlayQueue,
Alert,
AddStream,
UploadDialog,
},
computed: {

76
buildsystem/network-sharing-server/src/components/AddStream.vue

@ -0,0 +1,76 @@
<template>
<div class="container" v-if="this.appStore.showAddStream">
<div class="modal fade" id="streamModal" ref="streamModal" tabindex="-1" role="dialog"
aria-labelledby="streamModalTitle" @close="hidden" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="streamModalTitle" v-t="'NEW_STREAM'"></h5>
</div>
<div class="modal-body">
<div class="container">
<div class="d-flex align-items-center justify-content-center">
<input type="text" id="searchInput" class="search-input" ref="searchText"
:placeholder="$t('ENTER_STREAM')" @keydown.enter="onPressEnter">
<ImageButton type="send" data-bs-toggle="tooltip" data-bs-placement="bottom" v-on:click="play()" :title="$t('SEND')" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { useAppStore } from '../stores/AppStore'
import { mapStores } from 'pinia'
import { Modal } from 'bootstrap'
import ImageButton from '../components/ImageButton.vue'
import { nextTick } from 'vue'
export default {
name: 'App',
components: {
ImageButton,
},
computed: {
...mapStores(useAppStore)
},
data() {
return {
modal: null,
}
},
methods: {
play() {
this.$play({ path: this.$refs.searchText.value, id: -1 }, "stream", false, false)
this.appStore.showAddStream = false
},
hidden() {
this.appStore.showAddStream = false
}
},
mounted: function () {
this.appStore.$subscribe(() => {
if (this.appStore.showAddStream) {
this.modal = new Modal(document.getElementById('streamModal'), {})
this.modal.show()
nextTick(() => {
this.$refs.streamModal.addEventListener('hidden.bs.modal', () => {
this.appStore.showAddStream = false
})
})
} else {
if (this.modal) this.modal.hide()
}
})
},
}
</script>
<style>
</style>

2
buildsystem/network-sharing-server/src/components/MediaCardItem.vue

@ -46,7 +46,7 @@ export default {
return 'ratio-1x1 audio-img-container'
},
isBrowse() {
return (this.mediaType == 'folder' || this.mediaType == 'network')
return (this.mediaType == 'folder' || this.mediaType == 'network' || this.mediaType == 'stream' || this.mediaType == 'new-stream')
},
manageClick() {
if (['folder', 'storage', 'network'].includes(this.mediaType)) {

2
buildsystem/network-sharing-server/src/components/MediaListItem.vue

@ -42,7 +42,7 @@ export default {
return 'ratio-1x1 audio-img-container'
},
isBrowse() {
return (this.mediaType == 'folder' || this.mediaType == 'network')
return (this.mediaType == 'folder' || this.mediaType == 'network' || this.mediaType == 'stream' || this.mediaType == 'new-stream')
},
manageClick() {
if (['folder', 'storage', 'network'].includes(this.mediaType)) {

54
buildsystem/network-sharing-server/src/pages/BrowseList.vue

@ -1,4 +1,28 @@
<template>
<div class="container">
<h5 class="media-content text-primary">Streams</h5>
<div v-if="!favoritesLoaded" class="spinner-border text-primary mt-3" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<template v-else>
<div v-if="this.appStore.displayType[this.$route.name]" class="row gx-3 gy-3 media-list">
<MediaListItem :media="newStream" :mediaType="'new-stream'" :hideOverflow="true" />
<template v-for="stream in streams" :key="stream.id">
<MediaListItem :media="stream" :mediaType="'stream'" :hideOverflow="true" />
</template>
</div>
<div v-else class="row gx-3 gy-3 media-content">
<div class="col-md-3 col-lg-2 col-sm-4 col-6">
<MediaCardItem :media="newStream" :mediaType="'new-stream'" :hideOverflow="true" />
</div>
<div class="col-md-3 col-lg-2 col-sm-4 col-6" v-for="stream in streams" :key="stream.id">
<MediaCardItem :media="stream" :mediaType="'stream'" :hideOverflow="true" />
</div>
</div>
</template>
</div>
<div class="container">
<h5 class="media-content text-primary">Favorites</h5>
<div v-if="!favoritesLoaded" class="spinner-border text-primary mt-3" role="status">
@ -92,17 +116,46 @@ export default {
},
data() {
return {
streams: [],
favorites: [],
storages: [],
networkEntries: [],
streamsLoaded: false,
favoritesLoaded: false,
storagesLoaded: false,
networkLoaded: false,
forbidden: false,
networkForbidden: false,
newStream: {
"artist": "",
"artworkURL": "",
"id": -1,
"isFolder": true,
"length": 0,
"path": "",
"playing": false,
"resolution": "",
"title": this.$t('NEW_STREAM')
}
}
},
methods: {
fetchStreams() {
let component = this
http.get(vlcApi.streamList)
.catch(function (error) {
if (error.response !== undefined && error.response.status == 403) {
component.forbidden = true;
}
})
.then((response) => {
this.streamsLoaded = true;
if (response) {
component.forbidden = false;
this.streams = response.data
}
});
},
fetchFavorites() {
let component = this
http.get(vlcApi.favoriteList)
@ -161,6 +214,7 @@ export default {
}
},
created: function () {
this.fetchStreams();
this.fetchFavorites();
this.fetchStorages();
this.fetchNetwork();

4
buildsystem/network-sharing-server/src/plugins/api.js

@ -78,6 +78,10 @@ export const vlcApi = {
* Retrieve the favorite list API URL
*/
favoriteList: `${API_URL}favorite-list`,
/**
* Retrieve the stream list API URL
*/
streamList: `${API_URL}stream-list`,
/**
* Retrieve the storage list API URL
*/

3
buildsystem/network-sharing-server/src/plugins/vlcUtils.js

@ -37,6 +37,9 @@ export default {
}
app.config.globalProperties.$play = (media, mediaType, append, asAudio) => {
if (mediaType == "new-stream" && media.id == -1) {
appStore.showAddStream = true
} else
axios.get(vlcApi.play(media, mediaType, append, asAudio))
.catch(function (error) {
if (error.response.status != 200) {

1
buildsystem/network-sharing-server/src/stores/AppStore.js

@ -13,6 +13,7 @@ export const useAppStore = defineStore('app', {
displayType: reactive(useLocalStorage('displayType', {})),
warning: Object,
socketOpened: true,
showAddStream: false,
}),
getters: {
},

Loading…
Cancel
Save