Welcome on Planet VideoLAN. This page gathers the blogs and feeds of VideoLAN's developers and contributors. As such, it doesn't necessarly represent the opinion of all the developers, the VideoLAN project, ...
Using XML-RPC with Python3 is really simple. Calling system.version on http://localhost/RCP2 is as simple as:
import xmlrpc.client
proxy = xmlrpc.client.ServerProxy("http://localhost/RPC2")
print(proxy.system.version())
However, the default client is missing many features, like handling proxies. Using requests for the underlying connection allows …
At VideoLAN, we recently changed our signing procedure to leverage our security keys. As explained on Yubico website, Android signing is quite easy.
With this method, I can now sign the VLC releases on any of my computers without duplicating the keystore file. And keystore password is replaced by my Yubikey PIN.
Here is the precise process we went through to get this done.
I am considering a Debian based distribution with open JDK 8 installed for this post.
First of all, we need to install the pkcs11 opensc lib and the zipalign tool
sudo apt-get install opensc-pkcs11 zipalign
zipalign
can also be found in android_sdk_path/build-tools/version/
Then, we prepare the pkcs11 configuration. Let’s create file pkcs11_java.cfg
and fill it with:
name = OpenSC-PKCS11
description = SunPKCS11 via OpenSC
library = /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
slotListIndex = 0
Yubico How-To advises slotListIndex = 1
, but I had to set it to 0 to make it work with my Yubikey 4.
Let’s assume we save it in: ~/.pkcs11_java.cfg
If you did not set your management key, you have to do it now:
key=`dd if=/dev/random bs=1 count=24 2>/dev/null | hexdump -v -e '/1 "%02X"'`
echo $key
yubico-piv-tool -a set-mgm-key -n $key
Same for PIN setting, default one is 123456
yubico-piv-tool -a change-pin -P 123456 -N <NEW PIN>
Now it’s time to import our keystore to the key PIV slot.
keytool -importkeystore -srckeystore mykeystore.keystore -destkeystore mykeystorey.p12 -srcstoretype jks -deststoretype pkcs12
yubico-piv-tool -s 9a -a import-key -a import-cert -i mykeystorey.p12 -K PKCS12 -k
You will be asked to type in the keystore password, then the certificate management key.
Starting from now, you won’t have to type the keystore password anymore but your Yubikey PIN.
We can check that our key is ready to sign apps:
keytool -providerClass sun.security.pkcs11.SunPKCS11 -providerArg ~/.pkcs11_java.cfg -keystore NONE -storetype PKCS11 -list -J-Djava.security.debug=sunpkcs11
This is the Yubikey PIN you have to type-in now.
And don’t forget to touch it if you enabled the ‘touch-to-sign’ option.
We now have to get an unsigned apk, so we must tell gradle to not apply any signing config for release builds
buildTypes {
release {
signingConfig null
//…
}
}
Finally we can sign an apk without our keystore, we just need the Yubikey to be plugged and fire up jarsigner
jarsigner -providerClass sun.security.pkcs11.SunPKCS11 -providerArg ~/.pkcs11_java.cfg \
-keystore NONE -storetype PKCS11 -sigalg SHA1withRSA -digestalg SHA1 \
app.apk "Certificate for PIV Authentication"
We can now verify the package is signed:
jarsigner -verify app.apk
The -sigalg SHA1withRSA -digestalg SHA1
parameters are needed because we support old devices. If you don’t support Android 4.2 and older you can rip it off.
Here is the full bash script I use to sign all my apks at once:
#! /bin/sh
echo "Please enter Yubikey PIN code "
stty -echo
trap 'stty echo' EXIT
read -p 'PIN: ' YUBI_PIN
stty echo
trap - EXIT
echo "\nSigning apks\n"
for i in `ls *.apk`;
do
jarsigner -providerClass sun.security.pkcs11.SunPKCS11 \
-providerArg ~/.pkcs11_java.cfg -keystore NONE -storetype PKCS11 \
-sigalg SHA1withRSA -digestalg SHA1 -storepass $YUBI_PIN \
$i "Certificate for PIV Authentication"
zipalign 4 $i $i.tmp && mv -vf $i.tmp $i
done
unset YUBI_PIN
Since build tools 24.0.3, Google released apksigner, a newer signature tool with convenient arguments like --min-sdk-version
to get sure the application signature is correct.
But current release (25.0.3) doesn’t handle pkcs11 protocol correctly.
builds tools 26.0.0 have just been released and they do not contain apksigner anymore
We still can build apksigner from source to get upstream version which is able to manage our Yubikey!
git clone https://android.googlesource.com/platform/tools/apksig
We need Bazel to build this project, here are some instructions to install it Then go into apksig folder, create a Bazel workspace and let’s build it.
touch WORKSPACE
export WORKSPACE=`pwd`
bazel build :apksigner
And here is how we can sign with it:
~/tmp/apksig/bazel-bin/apksigner sign --ks NONE --ks-pass "pass:$YUBI_PIN" \
--min-sdk-version 9 --provider-class sun.security.pkcs11.SunPKCS11 \
--provider-arg pkcs11_java.cfg --ks-type PKCS11 app.apk
With apksigner, we need to zipalign the apk before signing them.
We can also verify apk is well signed:
~/tmp/apksig/bazel-bin/apksigner verify app.apk
And verify
accepts --min-sdk-version
and --max-sdk-version
to ensure your users won’t get 103
once the release is out.
Jinja2 is a powerful templating engine for Python.
Inside LAVA, we use Jinja2 to generate configuration files for every boards that we support.
The configuration is generated from a template that does inherit from a base template.
For instance, for a beaglebone-black called bbb-01, the template inheritance tree is the …
As stated in the previous post, we do process all DiffUtil.DiffResult calculations in main thread to preserve adapter state consistency.
But in VLC, we have to deal with potentially HUGE datasets, so calculation could take some time.
Background calculation is mandatory then, and we have to preserve dataset consistency. I lost a few days trying different techniques then finally chose to stack updates within a queue and use it for all dataset operations, because it provides consistency safetyness.
I’ve been inspired by Jon F Hancock blog post to get this right.
To achieve background calculation and preserve data consistency, we now have to use our update()
method for all dataset updates or manage the pending queue state manually.
update(list)
method is now splitted in two, in order to allow queueing and recursivity:
update(list)
which is now limited to queueing the new list and triggering internalUpdate(list)
to do the actual job.
Notice all queue accesses or modifications are done in the main thread (for the same reasons that for dataset changes).
// Our queue with next dataset
private final ArrayDeque<Item[]> mPendingUpdates = new ArrayDeque<>();
@MainThread
void update(final ArrayList<Item> newList) {
mPendingUpdates.add(newList);
if (mPendingUpdates.size() == 1)
internalUpdate(newList); //no pending update, let's go
}
//private method, called exclusively by update()
private void internalUpdate(final ArrayList<Item> newList) {
VLCApplication.runBackground(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new MediaItemDiffCallback(mDataset, newList), false);
//back to main thread for the update
VLCApplication.runOnMainThread(new Runnable() {
@Override
public void run() {
mDataset = newList;
result.dispatchUpdatesTo(BaseBrowserAdapter.this);
//We are done with this dataset
mPendingUpdates.remove();
//Process the next queued dataset if any
if (!mPendingUpdates.isEmpty())
internalUpdate(mPendingUpdates.peek());
}
});
}
});
}
For simple actions, like item insertion/removal, we must check the mPendingUpdates
state. Either we handle it, either we use update(list)
in order to respect the queue process we just set. So, we have to copy the most recent dataset, add/remove the item then call update(list)
.
Using mDataset
as the current reference state can be a mistake, if mPendingUpdates
is not empty, another dataset will be processed between mDataset
and our new list with item added or removed. In this case, we have to peek the last list from mPendingUpdates
.
@MainThread
void addItem(Item item) {
ArrayList<Item> newList = new ArrayList<>(mPendingUpdates.isEmpty() ? mDataset : mPendingUpdates.peekLast());
newList.add(item);
update(newList);
}
For item removal, I’d recommend to just avoid calling it with position only, prefer to pass the item reference. Because the position value is likely to be wrong if there is a pending update at this time.
In case you can receive a bunch of updates while DiffUtil is calculating the DiffUtil.DiffResult, you get a stack of new datasets to process. Let’s skip to the last one: as we made sure they are consistent we can do it. That’s just factorizing the updates.
We have to clear the mPendingUpdates
queue from all its elements but the last one.
Here is our current queue processing:
mPendingUpdates.remove();
if (!mPendingUpdates.isEmpty())
internalUpdate(mPendingUpdates.peek());
Which becomes:
mPendingUpdates.remove();
if (!mPendingUpdates.isEmpty()) {
if (mPendingUpdates.size() > 1) { // more than one update queued
ArrayList<Item> lastList = mPendingUpdates.peekLast();
mPendingUpdates.clear();
mPendingUpdates.add(lastList);
}
internalUpdate(mPendingUpdates.peek());
}
Here is my base adapter class, dedicated to pending queue management. Children classes just need to call update(newList)
for any update.
(I chose to not specify List<T>
because I also use arrays)
public abstract class BaseQueuedAdapter <T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
protected T mDataset;
private final ArrayDeque<T> mPendingUpdates = new ArrayDeque<>();
final Handler mHandler = new Handler(Looper.getMainLooper());
@MainThread
public boolean hasPendingUpdates() {
return !mPendingUpdates.isEmpty();
}
@MainThread
public T peekLast() {
return mPendingUpdates.isEmpty() ? mDataset : mPendingUpdates.peekLast();
}
@MainThread
public void update(final T items) {
mPendingUpdates.add(items);
if (mPendingUpdates.size() == 1)
internalUpdate(items);
}
private void internalUpdate(final T newList) {
new thread(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new ItemDiffCallback(mDataList, newList), false);
mHandler.post(new Runnable() {
@Override
public void run() {
mDataset = newList;
result.dispatchUpdatesTo(BaseQueuedAdapter.this);
processQueue();
}
});
}
}).start();
}
@MainThread
private void processQueue() {
mPendingUpdates.remove();
if (!mPendingUpdates.isEmpty()) {
if (mPendingUpdates.size() > 1) {
T lastList = mPendingUpdates.peekLast();
mPendingUpdates.clear();
mPendingUpdates.add(lastList);
}
internalUpdate(mPendingUpdates.peek());
}
}
}
My adapter class becomes:
public class MyAdapter extends BaseQueuedAdapter<List<Item>, MyAdapter.ViewHolder>
That’s it, we now have asynchronous and classy RecyclerView updates without extra boilerplate 😎
DiffUtil is an AppCompat utility class developed to ease Recyclerview updates.
You just pass it the new dataset and current one and it automagically handles all the notifyItemRange*
events to provide cool RecyclerView animations or subtle item updates (like a single progressbar update instead of the whole item layout rebinding).
diffResult.dispatchUpdatesTo(recyclerviewAdapter)
, your recyclerviewAdapter will receive all the corresponding notifyItemRange* events and benefit from shiny recyclerview animations!It is strongly advised to run all dataset operations in the main thread, to ensure consistency. And for now (until next post), DiffUtil calculations will also be run in the main thread for consistency concern.
Here is a simple example of DiffUtil powered recyclerview update method in browsers adapter:
@MainThread
void update(final ArrayList<Item> newList) {
final DiffUtil.DiffResult result = DiffUtil.calculateDiff(
new MediaItemDiffCallback(mDataset, newList), false);
mDataset = newList;
result.dispatchUpdatesTo(MyAdapter.this);
}
You can see we only have these 3 basic steps.
With mDataset
the current dataset, newList
the new one, and false
the parameter value for moves detection, we don’t handle it for now.
The base DiffUtil.Callback implementation we use:
public class MediaItemDiffCallback extends DiffUtil.Callback {
protected Item[] oldList, newList;
public MediaItemDiffCallback(Item[] oldList, Item[] newList) {
this.oldList = oldList;
this.newList = newList;
}
@Override
public int getOldListSize() {
return oldList == null ? 0 : oldList.length;
}
@Override
public int getNewListSize() {
return newList == null ? 0 : newList.length;
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList[oldItemPosition].equals(newList[newItemPosition]);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return true;
}
}
For now, areContentsTheSame
always returns true
, we’ll see later how to use it for smarter updates.
Let’s focus on areItemsTheSame
: this is simply a equals-ish method to determine if two instances correspond to the same model view.
Now, with this diffResult, for every item insertion and deletion we get the precise adapter notification.
Typical application is list feeding, all new items are nicely inserted or removed which makes fancy animations to happen, like if we had set all the notifyItemInserted
and notifyItemDeleted
one by one.
A nicer use case is list/grid filtering. Here’s the result, with that simple update(newList)
call:
In video section, we want to update an item view if media progress or thumbnail has changed, but we don’t want to completely rebind its view.
Until VLC 2.0.6, you could experience a flicker of the video you were watching once getting back from video player.
That’s because media progress had changed and we blindly updated the whole view (which triggered thumbnail reloading).
Here is the DiffUtil.Callback specific to video grid which not only checks if items are the same, but if their content has changed also:
private class VideoItemDiffCallback extends DiffUtil.Callback {
List<Item> oldList, newList;
VideoItemDiffCallback(List<Item> oldList, List<Item> newList) {
this.oldList = oldList;
this.newList = newList;
}
@Override
public int getOldListSize() {
return oldList == null ? 0 : oldList.size();
}
@Override
public int getNewListSize() {
return newList == null ? 0 : newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
Item oldItem = oldList.get(oldItemPosition);
Item newItem = newList.get(newItemPosition);
return oldItem.getTime() == newItem.getTime() &&
TextUtils.equals(oldItem.getArtworkMrl(), newItem.getArtworkMrl());
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
Item oldItem = oldList.get(oldItemPosition);
Item newItem = newList.get(newItemPosition);
if (oldItem.getTime() != newItem.getTime())
return UPDATE_TIME;
else
return UPDATE_THUMB;
}
}
If areItemsTheSame
returns true
, areContentsTheSame
is called for the same items. areContentsTheSame
returns false
if we detect small changes we want to propagate to the UI without totally rebinding the concerned item view.
In our case this is two Item
instances representing the same video file but artwork URL or time value has changed.
If areContentsTheSame
returns false
, getChangePayload
is called for the same items. We define here the updated value and return it. The dispatch util will call notifyItemChanged(position, payload)
with this value. Then it’s up to the adapter to manage the update:
In the adapter, we override onBindViewHolder(holder, position, payload)
to achieve this:
@Override
public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else {
MediaWrapper media = mVideos.get(position);
for (Object data : payloads) {
switch ((int) data) {
case UPDATE_THUMB:
AsyncImageLoader.loadPicture(holder.thumbView, media);
break;
case UPDATE_TIME:
fillView(holder, media);
break;
}
}
}
}
onBindViewHolder(holder, position, payloads)
makes the smart move and updates the related view(s), instead of rebinding the whole item view. In our case, we load the thumb in the ImageView
or we regenerate the progress string and update the progress bar with fillView()
.
As the dataSet is already updated, we don’t need to pass the values here, so I opted for constants refering to the different actions.
The DiffUtil.calculateDiff
algorithm can optionnally do a second pass to look for items movements between the old and new lists. If the sort order doesn’t change, it is useless to do it.
In VLC it’s only interesting in video grid for now, so we call:
DiffUtil.calculateDiff(new VideoItemDiffCallback(oldList, newList), detectMoves);
Where detectMoves
variable is a boolean which is set to true
only in case of video resorting call, in other cases we spare the second pass. Thanks to it, animations are fancier with only moving cards. Without this moves detection we’d get disappearing and reappearing cards.
Resorting videos with moves detection:
Without moves detection:
We now master diffutil, but we can do better and get calculation out of UI thread.
Rendez-vous to the second part of this post to check it out.
Not so much activity on Play Store since v2.0.6 release.
I’ve been quite busy but VLC keeps going on with very interesting evolutions!
This post will only deal with features, not their implementation. I will later publish other posts, more interesting for developers, about it.
2.1.x version name will be used for beta only, we’ll decide later which version the stable release will be.
To get it you need to join the beta program, and you’ll receive it like a normal update.
For now, let me introduce you to the new VLC for Android.
Video cards have been refactored, we now show you video informations over its cover picture. This is a nicer presentation and gives room for displaying more videos at once. Others lists view have been reworked too, like audio media, browsers and history. We got rid of cardview pattern, and got back to a more flat and clean design.
Audio lists have been a bit lifted too but this change is lighter.
Info page has also been redesigned with a fancy collapse effect for thumbnail or cover.
Audio player background is now a blurred version of current art cover if available.
The action bar will now hide when your scroll your media lists, to save the more useful space possible while you’re looking for your media.
Album view has been revamped to become more #Material and now serves for playlists too
Thanks to the DiffUtil tool from Appcompat library, grids/lists updates are now animated and more efficient.
One disturbing side effect is when you refresh and there’s no change in your media library. You won’t see any update, not even any flickering.
But the cool thing is whith actual content update, like during media scan or filtering the current view with the new search UX, insertions/deletions and position changes are animated:
TV interface had its own lifting too, nothing really impressive here. Colors are less depressive and we make long media titles scroll, I heard your (justified) frustration :)
The interesting new feature on TV is the Picture-In-Picture (aka PIP) mode, available for all Android TVs powered by Android 7+.
It’s really like our popup mode in classic phone/tablet interface (so we used the very same icon in video advanced options for PIP).
You can still watch your video while using another application.
VLC now supports 360° videos, you can change viewpoint by swiping or with remote control arrows.
Cardboard/VR mode is not available yet but we are working on it.
Search has been splitted in two modes:
SEARCH IN ALL MEDIALIBRARY
button to show the new search activity. Which will bring detailed results grouped by video/artist/album/songs/genres/playlistBonus:
VLC will now be compatible with voice search.
Asking Google Now “Search game in VLC” will trigger a game search and show you this new search result screen.
This release will bring Android Auto compatibility.
You’ll be able to use VLC as your travel music player with easy browsing into you audio library, with the minimum possible distraction from driving.
VLC also supports voice actions on Android Auto:
You can ask “play Daft Punk” and Google Assistant will recognize whether it’s an artist, an album or a song you’re asking for and trigger VLC to play it.
You can now select multiple items by long press on them (classic context menu is still available with the three dots icon) and enjoy actions like play it all or add selection to playlist
Actions available depend on media type and selection count.
This is very handy for starting or creating a playlist.
That’s the most important change in this update, because it affects the whole application, but you should barely notice it…
VLC now uses medialibrary like VLC for Tizen, others VLC ports will follow.
It’s a C++ library, written by Hugo Beauzée-Luyssen, which parses storages for video/audio files and manages a sqlite database to model your media library (with album, artist, genre classification, etc..). It replaces the old system we had on Android which just saved media files with their metadata, we had no proper structure for media library.
So these categories listing are now faster, we don’t have to generate categories at runtime. And this is all native code, which is faster than Java.
Beside this speed improvement, one of the main benefits of this medialibrary is to provide better search results
For now we are focusing on the first scan performance to make it at least as fast as the previous system.
So, this library is aimed to be a common module of all VLC ports, wich means all debugging, performance and any improvement will benefit other platforms.
Next steps for this library will be media scrapping, and network scan:
VLC core team worked hard too to bring performance improvements and some new features. Here are some highlights:
We also plan to implement a feature to download media on your device, in order to sync your series episodes or songs from your NAS to your device.
We’d like to support videos playlists like we do with videos grouped by their common name prefix.
As previously stated, medialibrary will allow to make VLC a real media center with fancy movies/tv shows presentation, and better artists/albums illustrations.
At last, I started an extension API in order for everyone to develop android applications which will provide content to VLC. For example, we will release (with sources of course) extensions bringing support of podcasts subscriptions and Google Drive access.
Here are some hidden features of VLC on Android.
VLC does not yet support Chromecast, but you already can use it!
Android can cast its screen content and audio to it. Here are some ways to access this option:
VLC detects the secondary display (your Chromecast) like it does if you plug your device to your TV with a HDMI cable, and video player will show you this screen:
You have normal controls like swipe to seek and go to advanced options while you watch your movie on TV 📺 😃
There are 2 ways to completely stop vlc player service (and not just pause playback), but this action has no explicit button.
With version 2.1 you’ll have the repeat button available in the video player advanced options, but you can already do that with version 2.0.x:
Long press on play/pause button to switch playback to repeat mode so your video playback will loop
Simply drag media within current playlist to reorder it.
In action bar, the circled play button triggers loading of last playlist from the current category: In video view it will load last video(s) played, in Audio view it will load last audio playlist.
With remote play/pause control (wired headset or bluetooth ad2p device) when VLC is not playing, you can load last audio playlist and continue playback where you stopped it.
Swipe miniplayer on left/right triggers skip to previous/next media.
If you have a keyboard or a gamepad connected to your Android device, here are the key bindings in video player:
within DVD menu:
Key(s) | Action |
---|---|
↓↑←→ | navigate |
Gamepad A or X /play/pause/space/OK/enter | click on focused item |
in 360° video playback (version 2.1.0+):
Key(s) | Action |
---|---|
↓↑←→ | change viewpoint |
normal case:
Key(s) | Action |
---|---|
F/fast_forward/→ | seek (+10 seconds) |
R/rewind/← | seek (-10 seconds) |
R1 | seek (+1 minute) |
L1 | seek (-1 minute) |
A(gamepad)/play/pause/space/OK | toggle play/pause |
O/Y(gamepad)/menu/↑ | show advanced options |
V/audio-track/X(gamepad) | open subtitles options |
N | show DVD navigation menu |
A | resize video |
M/mute | toggle sound mute |
S/STOP | stop playback & exit player |
captions | select subtitles |
J | delay audio (-50ms) |
K | delay audio (+50ms) |
G | delay subtitles (-50ms) |
H | delay subtitles (+50ms) |