Stubbed Media and LibVLC classes

Used Abstract Factory Manager design pattern for LibVLC components

Tests done: StreamsModel, SubtitlesModel, HistoryModel, FilePickerModel,
       BrowserModel, StorageModel, FileBrowserModel, NetworkModel,
       VideosViewModel

Used CoroutineContextProvider to replace context on-demand.
Added extension function for the child of SingletonHolder used in ExternalSubRepository.
Replaced OpenSubtitleRepository.getInstance to use lazy value, so it can be replaced in tests.
Added Dependency Provider for BrowserProvider
Updated StubDataSource to configure data set to provide
LibVLC: Refactored interfaces

Signed-off-by: Shivansh Saini <shivanshs9@gmail.com>
This commit is contained in:
Shivansh Saini 2019-05-31 12:06:21 +05:30 committed by Geoffrey Métais
parent 3374f11ae6
commit a2896d2b4b
83 changed files with 2786 additions and 822 deletions

View File

@ -58,6 +58,7 @@ ext {
espressoVersion = '3.1.1'
livedataTest = '1.1.0'
robolectric = '4.2.1'
mockk = '1.9.3'
supportTest = '1.1.0'
// versionCode scheme is T M NN RR AA
// T: Target/Flavour (1 for Android, 2 for Chrome?)

View File

@ -31,6 +31,8 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import org.videolan.libvlc.interfaces.IVLCVout;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

View File

@ -24,11 +24,13 @@ import android.os.Handler;
import android.os.Looper;
import androidx.annotation.MainThread;
import org.videolan.libvlc.interfaces.ILibVLC;
@SuppressWarnings("unused, JniMissingFunction")
public abstract class Dialog {
/**
* Dialog Callback, see {@link Dialog#setCallbacks(LibVLC, Callbacks)}
* Dialog Callback, see {@link Dialog#setCallbacks(ILibVLC, Callbacks)}
*/
public interface Callbacks {
/**
@ -164,15 +166,15 @@ public abstract class Dialog {
/**
* Register callbacks in order to handle VLC dialogs
*
* @param libVLC valid LibVLC object
* @param ILibVLC valid LibVLC object
* @param callbacks dialog callbacks or null to unregister
*/
@MainThread
public static void setCallbacks(LibVLC libVLC, Callbacks callbacks) {
public static void setCallbacks(ILibVLC ILibVLC, Callbacks callbacks) {
if (callbacks != null && sHandler == null)
sHandler = new Handler(Looper.getMainLooper());
sCallbacks = callbacks;
nativeSetCallbacks(libVLC, callbacks != null);
nativeSetCallbacks(ILibVLC, callbacks != null);
}
/**
@ -476,5 +478,5 @@ public abstract class Dialog {
});
}
private static native void nativeSetCallbacks(LibVLC libVLC, boolean enabled);
private static native void nativeSetCallbacks(ILibVLC ILibVLC, boolean enabled);
}

View File

@ -0,0 +1,18 @@
package org.videolan.libvlc;
import org.videolan.libvlc.interfaces.IComponentFactory;
import java.util.HashMap;
import java.util.Map;
public class FactoryManager {
private static Map<String, IComponentFactory> factories = new HashMap<>();
public static void registerFactory(String factoryId, IComponentFactory factory) {
factories.put(factoryId, factory);
}
public static IComponentFactory getFactory(String factoryId) {
return factories.get(factoryId);
}
}

View File

@ -23,18 +23,22 @@ package org.videolan.libvlc;
import android.content.Context;
import android.util.Log;
import androidx.annotation.Nullable;
import org.videolan.libvlc.interfaces.AbstractVLCEvent;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.util.HWDecoderUtil;
import java.util.ArrayList;
import androidx.annotation.Nullable;
import java.util.List;
@SuppressWarnings("unused, JniMissingFunction")
public class LibVLC extends VLCObject<LibVLC.Event> {
public class LibVLC extends VLCObject<ILibVLC.Event> implements ILibVLC {
private static final String TAG = "VLC/LibVLC";
final Context mAppContext;
public static class Event extends VLCEvent {
public static class Event extends AbstractVLCEvent {
protected Event(int type) {
super(type);
}
@ -45,12 +49,12 @@ public class LibVLC extends VLCObject<LibVLC.Event> {
*
* @param options
*/
public LibVLC(Context context, ArrayList<String> options) {
public LibVLC(Context context, List<String> options) {
mAppContext = context.getApplicationContext();
loadLibraries();
if (options == null)
options = new ArrayList<String>();
options = new ArrayList<>();
boolean setAout = true, setChroma = true;
// check if aout/vout options are already set
for (String option : options) {
@ -88,27 +92,35 @@ public class LibVLC extends VLCObject<LibVLC.Event> {
/**
* Get the libVLC version
*
* @return the libVLC version string
*/
public native String version();
/**
* Get the libVLC compiler
*
* @return the libVLC compiler string
*/
public native String compiler();
/**
* Get the libVLC changeset
*
* @return the libVLC changeset string
*/
public native String changeset();
@Override
protected Event onEventNative(int eventType, long arg1, long arg2, float argf1, @Nullable String args1) {
protected ILibVLC.Event onEventNative(int eventType, long arg1, long arg2, float argf1, @Nullable String args1) {
return null;
}
@Override
public Context getAppContext() {
return mAppContext;
}
@Override
protected void onReleaseNative() {
nativeRelease();
@ -121,13 +133,15 @@ public class LibVLC extends VLCObject<LibVLC.Event> {
* @param name human-readable application name, e.g. "FooBar player 1.2.3"
* @param http HTTP User Agent, e.g. "FooBar/1.2.3 Python/2.6.0"
*/
public void setUserAgent(String name, String http){
public void setUserAgent(String name, String http) {
nativeSetUserAgent(name, http);
}
/* JNI */
private native void nativeNew(String[] options, String homePath);
private native void nativeRelease();
private native void nativeSetUserAgent(String name, String http);
private static boolean sLoaded = false;

View File

@ -0,0 +1,24 @@
package org.videolan.libvlc;
import android.content.Context;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.interfaces.ILibVLCFactory;
import java.util.List;
public class LibVLCFactory implements ILibVLCFactory {
static {
FactoryManager.registerFactory(ILibVLCFactory.factoryId, new LibVLCFactory());
}
@Override
public ILibVLC getFromOptions(Context context, List<String> options) {
return new LibVLC(context, options);
}
@Override
public ILibVLC getFromContext(Context context) {
return new LibVLC(context, null);
}
}

View File

@ -23,6 +23,9 @@ package org.videolan.libvlc;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.interfaces.IMediaList;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.libvlc.util.HWDecoderUtil;
import org.videolan.libvlc.util.VLCUtil;
@ -32,171 +35,9 @@ import java.io.FileDescriptor;
import androidx.annotation.Nullable;
@SuppressWarnings("unused, JniMissingFunction")
public class Media extends VLCObject<Media.Event> {
public class Media extends VLCObject<IMedia.Event> implements IMedia {
private final static String TAG = "LibVLC/Media";
public static class Event extends VLCEvent {
public static final int MetaChanged = 0;
public static final int SubItemAdded = 1;
public static final int DurationChanged = 2;
public static final int ParsedChanged = 3;
//public static final int Freed = 4;
public static final int StateChanged = 5;
public static final int SubItemTreeAdded = 6;
protected Event(int type) {
super(type);
}
protected Event(int type, long arg1) {
super(type, arg1);
}
public int getMetaId() {
return (int) arg1;
}
/**
* Get the ParsedStatus in case of {@link Event#ParsedChanged} event
* @return {@link Media.ParsedStatus}
*/
public int getParsedStatus() {
return (int) arg1;
}
}
public interface EventListener extends VLCEvent.Listener<Media.Event> {}
/**
* libvlc_media_type_t
*/
public static class Type {
public static final int Unknown = 0;
public static final int File = 1;
public static final int Directory = 2;
public static final int Disc = 3;
public static final int Stream = 4;
public static final int Playlist = 5;
}
/**
* see libvlc_meta_t
*/
public static class Meta {
public static final int Title = 0;
public static final int Artist = 1;
public static final int Genre = 2;
public static final int Copyright = 3;
public static final int Album = 4;
public static final int TrackNumber = 5;
public static final int Description = 6;
public static final int Rating = 7;
public static final int Date = 8;
public static final int Setting = 9;
public static final int URL = 10;
public static final int Language = 11;
public static final int NowPlaying = 12;
public static final int Publisher = 13;
public static final int EncodedBy = 14;
public static final int ArtworkURL = 15;
public static final int TrackID = 16;
public static final int TrackTotal = 17;
public static final int Director = 18;
public static final int Season = 19;
public static final int Episode = 20;
public static final int ShowName = 21;
public static final int Actors = 22;
public static final int AlbumArtist = 23;
public static final int DiscNumber = 24;
public static final int MAX = 25;
}
/**
* see libvlc_state_t
*/
public static class State {
public static final int NothingSpecial = 0;
public static final int Opening = 1;
/* deprecated public static final int Buffering = 2; */
public static final int Playing = 3;
public static final int Paused = 4;
public static final int Stopped = 5;
public static final int Ended = 6;
public static final int Error = 7;
public static final int MAX = 8;
}
/**
* see libvlc_media_parse_flag_t
*/
public static class Parse {
public static final int ParseLocal = 0;
public static final int ParseNetwork = 0x01;
public static final int FetchLocal = 0x02;
public static final int FetchNetwork = 0x04;
public static final int DoInteract = 0x08;
}
/*
* see libvlc_media_parsed_status_t
*/
public static class ParsedStatus {
public static final int Skipped = 1;
public static final int Failed = 2;
public static final int Timeout = 3;
public static final int Done = 4;
}
/**
* see libvlc_media_track_t
*/
public static abstract class Track {
public static class Type {
public static final int Unknown = -1;
public static final int Audio = 0;
public static final int Video = 1;
public static final int Text = 2;
}
public final int type;
public final String codec;
public final String originalCodec;
public final int id;
public final int profile;
public final int level;
public final int bitrate;
public final String language;
public final String description;
private Track(int type, String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description) {
this.type = type;
this.codec = codec;
this.originalCodec = originalCodec;
this.id = id;
this.profile = profile;
this.level = level;
this.bitrate = bitrate;
this.language = language;
this.description = description;
}
}
/**
* see libvlc_audio_track_t
*/
public static class AudioTrack extends Track {
public final int channels;
public final int rate;
private AudioTrack(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description,
int channels, int rate) {
super(Type.Audio, codec, originalCodec, id, profile, level, bitrate, language, description);
this.channels = channels;
this.rate = rate;
}
}
@SuppressWarnings("unused") /* Used from JNI */
private static Track createAudioTrackFromNative(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description,
@ -206,61 +47,6 @@ public class Media extends VLCObject<Media.Event> {
channels, rate);
}
/**
* see libvlc_video_track_t
*/
public static class VideoTrack extends Track {
public static final class Orientation {
/** Top line represents top, left column left */
public static final int TopLeft = 0;
/** Flipped horizontally */
public static final int TopRight = 1;
/** Flipped vertically */
public static final int BottomLeft = 2;
/** Rotated 180 degrees */
public static final int BottomRight = 3;
/** Transposed */
public static final int LeftTop = 4;
/** Rotated 90 degrees clockwise (or 270 anti-clockwise) */
public static final int LeftBottom = 5;
/** Rotated 90 degrees anti-clockwise */
public static final int RightTop= 6;
/** Anti-transposed */
public static final int RightBottom = 7;
}
public static final class Projection {
public static final int Rectangular = 0;
/** 360 spherical */
public static final int EquiRectangular = 1;
public static final int CubemapLayoutStandard = 0x100;
}
public final int height;
public final int width;
public final int sarNum;
public final int sarDen;
public final int frameRateNum;
public final int frameRateDen;
public final int orientation;
public final int projection;
private VideoTrack(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description,
int height, int width, int sarNum, int sarDen, int frameRateNum, int frameRateDen,
int orientation, int projection) {
super(Type.Video, codec, originalCodec, id, profile, level, bitrate, language, description);
this.height = height;
this.width = width;
this.sarNum = sarNum;
this.sarDen = sarDen;
this.frameRateNum = frameRateNum;
this.frameRateDen = frameRateDen;
this.orientation = orientation;
this.projection = projection;
}
}
@SuppressWarnings("unused") /* Used from JNI */
private static Track createVideoTrackFromNative(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description,
@ -271,20 +57,6 @@ public class Media extends VLCObject<Media.Event> {
height, width, sarNum, sarDen, frameRateNum, frameRateDen, orientation, projection);
}
/**
* see libvlc_subtitle_track_t
*/
public static class SubtitleTrack extends Track {
public final String encoding;
private SubtitleTrack(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description,
String encoding) {
super(Type.Text, codec, originalCodec, id, profile, level, bitrate, language, description);
this.encoding = encoding;
}
}
@SuppressWarnings("unused") /* Used from JNI */
private static Track createSubtitleTrackFromNative(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description,
@ -294,16 +66,6 @@ public class Media extends VLCObject<Media.Event> {
encoding);
}
/**
* see libvlc_subtitle_track_t
*/
public static class UnknownTrack extends Track {
private UnknownTrack(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description) {
super(Type.Unknown, codec, originalCodec, id, profile, level, bitrate, language, description);
}
}
@SuppressWarnings("unused") /* Used from JNI */
private static Track createUnknownTrackFromNative(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description) {
@ -311,78 +73,11 @@ public class Media extends VLCObject<Media.Event> {
level, bitrate, language, description);
}
/**
* see libvlc_media_slave_t
*/
public static class Slave {
public static class Type {
public static final int Subtitle = 0;
public static final int Audio = 1;
}
/** @see Type */
public final int type;
/** From 0 (low priority) to 4 (high priority) */
public final int priority;
public final String uri;
public Slave(int type, int priority, String uri) {
this.type = type;
this.priority = priority;
this.uri = uri;
}
}
@SuppressWarnings("unused") /* Used from JNI */
private static Slave createSlaveFromNative(int type, int priority, String uri) {
return new Slave(type, priority, uri);
}
/**
* see libvlc_media_stats_t
*/
public static class Stats {
public final int readBytes;
public final float inputBitrate;
public final int demuxReadBytes;
public final float demuxBitrate;
public final int demuxCorrupted;
public final int demuxDiscontinuity;
public final int decodedVideo;
public final int decodedAudio;
public final int displayedPictures;
public final int lostPictures;
public final int playedAbuffers;
public final int lostAbuffers;
public final int sentPackets;
public final int sentBytes;
public final float sendBitrate;
public Stats(int readBytes, float inputBitrate, int demuxReadBytes,
float demuxBitrate, int demuxCorrupted,
int demuxDiscontinuity, int decodedVideo, int decodedAudio,
int displayedPictures, int lostPictures, int playedAbuffers,
int lostAbuffers, int sentPackets, int sentBytes,
float sendBitrate) {
this.readBytes = readBytes;
this.inputBitrate = inputBitrate;
this.demuxReadBytes = demuxReadBytes;
this.demuxBitrate = demuxBitrate;
this.demuxCorrupted = demuxCorrupted;
this.demuxDiscontinuity = demuxDiscontinuity;
this.decodedVideo = decodedVideo;
this.decodedAudio = decodedAudio;
this.displayedPictures = displayedPictures;
this.lostPictures = lostPictures;
this.playedAbuffers = playedAbuffers;
this.lostAbuffers = lostAbuffers;
this.sentPackets = sentPackets;
this.sentBytes = sentBytes;
this.sendBitrate = sendBitrate;
}
}
@SuppressWarnings("unused") /* Used from JNI */
private static Stats createStatsFromNative(int readBytes,
float inputBitrate,
@ -426,50 +121,50 @@ public class Media extends VLCObject<Media.Event> {
/**
* Create a Media from libVLC and a local path starting with '/'.
*
* @param libVLC a valid libVLC
* @param ILibVLC a valid libVLC
* @param path an absolute local path
*/
public Media(LibVLC libVLC, String path) {
super(libVLC);
nativeNewFromPath(libVLC, path);
public Media(ILibVLC ILibVLC, String path) {
super(ILibVLC);
nativeNewFromPath(ILibVLC, path);
mUri = VLCUtil.UriFromMrl(nativeGetMrl());
}
/**
* Create a Media from libVLC and a Uri
*
* @param libVLC a valid libVLC
* @param ILibVLC a valid libVLC
* @param uri a valid RFC 2396 Uri
*/
public Media(LibVLC libVLC, Uri uri) {
super(libVLC);
nativeNewFromLocation(libVLC, VLCUtil.encodeVLCUri(uri));
public Media(ILibVLC ILibVLC, Uri uri) {
super(ILibVLC);
nativeNewFromLocation(ILibVLC, VLCUtil.encodeVLCUri(uri));
mUri = uri;
}
/**
* Create a Media from libVLC and a FileDescriptor
*
* @param libVLC a valid LibVLC
* @param ILibVLC a valid LibVLC
* @param fd file descriptor object
*/
public Media(LibVLC libVLC, FileDescriptor fd) {
super(libVLC);
nativeNewFromFd(libVLC, fd);
public Media(ILibVLC ILibVLC, FileDescriptor fd) {
super(ILibVLC);
nativeNewFromFd(ILibVLC, fd);
mUri = VLCUtil.UriFromMrl(nativeGetMrl());
}
/**
* Create a Media from libVLC and an AssetFileDescriptor
*
* @param libVLC a valid LibVLC
* @param ILibVLC a valid LibVLC
* @param afd asset file descriptor object
*/
public Media(LibVLC libVLC, AssetFileDescriptor afd) {
super(libVLC);
public Media(ILibVLC ILibVLC, AssetFileDescriptor afd) {
super(ILibVLC);
long offset = afd.getStartOffset();
long length = afd.getLength();
nativeNewFromFdWithOffsetLength(libVLC, afd.getFileDescriptor(), offset, length);
nativeNewFromFdWithOffsetLength(ILibVLC, afd.getFileDescriptor(), offset, length);
mUri = VLCUtil.UriFromMrl(nativeGetMrl());
}
@ -478,7 +173,7 @@ public class Media extends VLCObject<Media.Event> {
* @param ml Should not be released and locked
* @param index index of the Media from the MediaList
*/
protected Media(MediaList ml, int index) {
protected Media(IMediaList ml, int index) {
super(ml);
if (ml == null || ml.isReleased())
throw new IllegalArgumentException("MediaList is null or released");
@ -794,7 +489,7 @@ public class Media extends VLCObject<Media.Event> {
/**
* Enable HWDecoder options if not already set
*/
protected void setDefaultMediaPlayerOptions() {
public void setDefaultMediaPlayerOptions() {
boolean codecOptionSet;
synchronized (this) {
codecOptionSet = mCodecOptionSet;
@ -875,11 +570,11 @@ public class Media extends VLCObject<Media.Event> {
}
/* JNI */
private native void nativeNewFromPath(LibVLC libVLC, String path);
private native void nativeNewFromLocation(LibVLC libVLC, String location);
private native void nativeNewFromFd(LibVLC libVLC, FileDescriptor fd);
private native void nativeNewFromFdWithOffsetLength(LibVLC libVLC, FileDescriptor fd, long offset, long length);
private native void nativeNewFromMediaList(MediaList ml, int index);
private native void nativeNewFromPath(ILibVLC ILibVLC, String path);
private native void nativeNewFromLocation(ILibVLC ILibVLC, String location);
private native void nativeNewFromFd(ILibVLC ILibVLC, FileDescriptor fd);
private native void nativeNewFromFdWithOffsetLength(ILibVLC ILibVLC, FileDescriptor fd, long offset, long length);
private native void nativeNewFromMediaList(IMediaList ml, int index);
private native void nativeRelease();
private native boolean nativeParseAsync(int flags, int timeout);
private native boolean nativeParse(int flags);

View File

@ -22,11 +22,14 @@ package org.videolan.libvlc;
import androidx.annotation.Nullable;
import org.videolan.libvlc.interfaces.AbstractVLCEvent;
import org.videolan.libvlc.interfaces.ILibVLC;
@SuppressWarnings("unused, JniMissingFunction")
public class MediaDiscoverer extends VLCObject<MediaDiscoverer.Event> {
private final static String TAG = "LibVLC/MediaDiscoverer";
public static class Event extends VLCEvent {
public static class Event extends AbstractVLCEvent {
public static final int Started = 0x500;
public static final int Ended = 0x501;
@ -65,19 +68,19 @@ public class MediaDiscoverer extends VLCObject<MediaDiscoverer.Event> {
return new Description(name, longName, category);
}
public interface EventListener extends VLCEvent.Listener<MediaDiscoverer.Event> {}
public interface EventListener extends AbstractVLCEvent.Listener<MediaDiscoverer.Event> {}
private MediaList mMediaList = null;
/**
* Create a MediaDiscover.
*
* @param libVLC a valid LibVLC
* @param ILibVLC a valid LibVLC
* @param name Name of the vlc service discovery ("dsm", "upnp", "bonjour"...).
*/
public MediaDiscoverer(LibVLC libVLC, String name) {
super(libVLC);
nativeNew(libVLC, name);
public MediaDiscoverer(ILibVLC ILibVLC, String name) {
super(ILibVLC);
nativeNew(ILibVLC, name);
}
/**
@ -148,14 +151,14 @@ public class MediaDiscoverer extends VLCObject<MediaDiscoverer.Event> {
* @param category see {@link Description.Category}
*/
@Nullable
public static Description[] list(LibVLC libVLC, int category) {
return nativeList(libVLC, category);
public static Description[] list(ILibVLC ILibVLC, int category) {
return nativeList(ILibVLC, category);
}
/* JNI */
private native void nativeNew(LibVLC libVLC, String name);
private native void nativeNew(ILibVLC ILibVLC, String name);
private native void nativeRelease();
private native boolean nativeStart();
private native void nativeStop();
private static native Description[] nativeList(LibVLC libVLC, int category);
private static native Description[] nativeList(ILibVLC ILibVLC, int category);
}

View File

@ -0,0 +1,36 @@
package org.videolan.libvlc;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.interfaces.IMediaFactory;
import java.io.FileDescriptor;
public class MediaFactory implements IMediaFactory {
static {
FactoryManager.registerFactory(IMediaFactory.factoryId, new MediaFactory());
}
@Override
public IMedia getFromLocalPath(ILibVLC ILibVLC, String path) {
return new Media(ILibVLC, path);
}
@Override
public IMedia getFromUri(ILibVLC ILibVLC, Uri uri) {
return new Media(ILibVLC, uri);
}
@Override
public IMedia getFromFileDescriptor(ILibVLC ILibVLC, FileDescriptor fd) {
return new Media(ILibVLC, fd);
}
@Override
public IMedia getFromAssetFileDescriptor(ILibVLC ILibVLC, AssetFileDescriptor assetFileDescriptor) {
return new Media(ILibVLC, assetFileDescriptor);
}
}

View File

@ -25,46 +25,16 @@ import android.util.SparseArray;
import androidx.annotation.Nullable;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.interfaces.IMediaList;
@SuppressWarnings("unused, JniMissingFunction")
public class MediaList extends VLCObject<MediaList.Event> {
public class MediaList extends VLCObject<IMediaList.Event> implements IMediaList {
private final static String TAG = "LibVLC/MediaList";
public static class Event extends VLCEvent {
public static final int ItemAdded = 0x200;
//public static final int WillAddItem = 0x201;
public static final int ItemDeleted = 0x202;
//public static final int WillDeleteItem = 0x203;
public static final int EndReached = 0x204;
/**
* In case of ItemDeleted, the media will be already released. If it's released, cached
* attributes are still available (like {@link Media#getUri()}}).
*/
public final Media media;
private final boolean retain;
public final int index;
protected Event(int type, Media media, boolean retain, int index) {
super(type);
if (retain && (media == null || !media.retain()))
throw new IllegalStateException("invalid media reference");
this.media = media;
this.retain = retain;
this.index = index;
}
@Override
void release() {
if (retain)
media.release();
}
}
public interface EventListener extends VLCEvent.Listener<MediaList.Event> {}
private int mCount = 0;
private final SparseArray<Media> mMediaArray = new SparseArray<Media>();
private final SparseArray<IMedia> mMediaArray = new SparseArray<IMedia>();
private boolean mLocked = false;
private void init() {
@ -77,16 +47,16 @@ public class MediaList extends VLCObject<MediaList.Event> {
/**
* Create a MediaList from libVLC
* @param libVLC a valid libVLC
*
* @param ILibVLC a valid libVLC
*/
public MediaList(LibVLC libVLC) {
super(libVLC);
nativeNewFromLibVlc(libVLC);
public MediaList(ILibVLC ILibVLC) {
super(ILibVLC);
nativeNewFromLibVlc(ILibVLC);
init();
}
/**
*
* @param md Should not be released
*/
protected MediaList(MediaDiscoverer md) {
@ -96,27 +66,26 @@ public class MediaList extends VLCObject<MediaList.Event> {
}
/**
*
* @param m Should not be released
*/
protected MediaList(Media m) {
protected MediaList(IMedia m) {
super(m);
nativeNewFromMedia(m);
init();
}
private synchronized Media insertMediaFromEvent(int index) {
private synchronized IMedia insertMediaFromEvent(int index) {
for (int i = mCount - 1; i >= index; --i)
mMediaArray.put(i + 1, mMediaArray.valueAt(i));
mCount++;
final Media media = new Media(this, index);
final IMedia media = new Media(this, index);
mMediaArray.put(index, media);
return media;
}
private synchronized Media removeMediaFromEvent(int index) {
private synchronized IMedia removeMediaFromEvent(int index) {
mCount--;
final Media media = mMediaArray.get(index);
final IMedia media = mMediaArray.get(index);
if (media != null)
media.release();
for (int i = index; i < mCount; ++i) {
@ -130,7 +99,7 @@ public class MediaList extends VLCObject<MediaList.Event> {
}
@Override
protected synchronized Event onEventNative(int eventType, long arg1, long arg2, float argf1, @Nullable String args1) {
protected synchronized Event onEventNative(int eventType, long arg1, long arg2, float argf1, @Nullable String args1) {
if (mLocked)
throw new IllegalStateException("already locked from event callback");
mLocked = true;
@ -138,23 +107,23 @@ public class MediaList extends VLCObject<MediaList.Event> {
int index;
switch (eventType) {
case Event.ItemAdded:
index = (int) arg1;
if (index != -1) {
final Media media = insertMediaFromEvent(index);
event = new Event(eventType, media, true, index);
}
break;
case Event.ItemDeleted:
index = (int) arg1;
if (index != -1) {
final Media media = removeMediaFromEvent(index);
event = new Event(eventType, media, false, index);
}
break;
case Event.EndReached:
event = new Event(eventType, null, false, -1);
break;
case Event.ItemAdded:
index = (int) arg1;
if (index != -1) {
final IMedia media = insertMediaFromEvent(index);
event = new Event(eventType, media, true, index);
}
break;
case Event.ItemDeleted:
index = (int) arg1;
if (index != -1) {
final IMedia media = removeMediaFromEvent(index);
event = new Event(eventType, media, false, index);
}
break;
case Event.EndReached:
event = new Event(eventType, null, false, -1);
break;
}
mLocked = false;
return event;
@ -173,10 +142,10 @@ public class MediaList extends VLCObject<MediaList.Event> {
* @param index index of the media
* @return Media hold by MediaList. This Media should be released with {@link #release()}.
*/
public synchronized Media getMediaAt(int index) {
public synchronized IMedia getMediaAt(int index) {
if (index < 0 || index >= getCount())
throw new IndexOutOfBoundsException();
final Media media = mMediaArray.get(index);
final IMedia media = mMediaArray.get(index);
media.retain();
return media;
}
@ -184,7 +153,7 @@ public class MediaList extends VLCObject<MediaList.Event> {
@Override
public void onReleaseNative() {
for (int i = 0; i < mMediaArray.size(); ++i) {
final Media media = mMediaArray.get(i);
final IMedia media = mMediaArray.get(i);
if (media != null)
media.release();
}
@ -206,16 +175,22 @@ public class MediaList extends VLCObject<MediaList.Event> {
nativeUnlock();
}
protected synchronized boolean isLocked() {
public synchronized boolean isLocked() {
return mLocked;
}
/* JNI */
private native void nativeNewFromLibVlc(LibVLC libvlc);
private native void nativeNewFromLibVlc(ILibVLC libvlc);
private native void nativeNewFromMediaDiscoverer(MediaDiscoverer md);
private native void nativeNewFromMedia(Media m);
private native void nativeNewFromMedia(IMedia m);
private native void nativeRelease();
private native int nativeGetCount();
private native void nativeLock();
private native void nativeUnlock();
}

View File

@ -40,6 +40,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import org.videolan.libvlc.interfaces.AbstractVLCEvent;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.interfaces.IVLCVout;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.libvlc.util.DisplayManager;
import org.videolan.libvlc.util.VLCUtil;
@ -51,7 +55,7 @@ import java.io.IOException;
@SuppressWarnings("unused, JniMissingFunction")
public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
public static class Event extends VLCEvent {
public static class Event extends AbstractVLCEvent {
public static final int MediaChanged = 0x100;
//public static final int NothingSpecial = 0x101;
public static final int Opening = 0x102;
@ -141,7 +145,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
}
}
public interface EventListener extends VLCEvent.Listener<MediaPlayer.Event> {}
public interface EventListener extends AbstractVLCEvent.Listener<MediaPlayer.Event> {}
public static class Position {
public static final int Disable = -1;
@ -396,7 +400,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
}
public static final int SURFACE_SCALES_COUNT = ScaleType.values().length;
private Media mMedia = null;
private IMedia mMedia = null;
private RendererItem mRenderer = null;
private AssetFileDescriptor mAfd = null;
private boolean mPlaying = false;
@ -503,11 +507,11 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
private void registerAudioPlugV21(boolean register) {
if (register) {
final IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG);
final Intent stickyIntent = mLibVLC.mAppContext.registerReceiver(mAudioPlugReceiver, intentFilter);
final Intent stickyIntent = mILibVLC.getAppContext().registerReceiver(mAudioPlugReceiver, intentFilter);
if (stickyIntent != null)
mAudioPlugReceiver.onReceive(mLibVLC.mAppContext, stickyIntent);
mAudioPlugReceiver.onReceive(mILibVLC.getAppContext(), stickyIntent);
} else {
mLibVLC.mAppContext.unregisterReceiver(mAudioPlugReceiver);
mILibVLC.getAppContext().unregisterReceiver(mAudioPlugReceiver);
}
}
@ -560,7 +564,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
@TargetApi(Build.VERSION_CODES.M)
private void registerAudioPlugV23(boolean register) {
AudioManager am = (AudioManager) mLibVLC.mAppContext.getSystemService(Context.AUDIO_SERVICE);
AudioManager am = (AudioManager) mILibVLC.getAppContext().getSystemService(Context.AUDIO_SERVICE);
if (register) {
mAudioDeviceCallback.onAudioDevicesAdded(am.getDevices(AudioManager.GET_DEVICES_OUTPUTS));
am.registerAudioDeviceCallback(mAudioDeviceCallback, null);
@ -582,11 +586,11 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
/**
* Create an empty MediaPlayer
*
* @param libVLC a valid libVLC
* @param ILibVLC a valid libVLC
*/
public MediaPlayer(LibVLC libVLC) {
super(libVLC);
nativeNewFromLibVlc(libVLC, mWindow);
public MediaPlayer(ILibVLC ILibVLC) {
super(ILibVLC);
nativeNewFromLibVlc(ILibVLC, mWindow);
}
/**
@ -594,7 +598,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
*
* @param media a valid Media object
*/
public MediaPlayer(@NonNull Media media) {
public MediaPlayer(@NonNull IMedia media) {
super(media);
if (media == null || media.isReleased())
throw new IllegalArgumentException("Media is null or released");
@ -663,7 +667,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
*
* @param media a valid Media object
*/
public void setMedia(@Nullable Media media) {
public void setMedia(@Nullable IMedia media) {
if (media != null) {
if (media.isReleased())
throw new IllegalArgumentException("Media is released");
@ -703,7 +707,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
* Get the Media used by this MediaPlayer. This Media should be released with {@link #release()}.
*/
@Nullable
public synchronized Media getMedia() {
public synchronized IMedia getMedia() {
if (mMedia != null)
mMedia.retain();
return mMedia;
@ -751,7 +755,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
* @param afd The {@link AssetFileDescriptor} to play
*/
public void play(@NonNull AssetFileDescriptor afd) {
final Media media = new Media(mLibVLC, afd);
final IMedia media = new Media(mILibVLC, afd);
play(media);
}
@ -760,7 +764,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
* @param path Path of the media file to play
*/
public void play(@NonNull String path) {
final Media media = new Media(mLibVLC, path);
final IMedia media = new Media(mILibVLC, path);
play(media);
}
@ -769,15 +773,15 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
* @param uri {@link Uri} of the media to play
*/
public void play(@NonNull Uri uri) {
final Media media = new Media(mLibVLC, uri);
final IMedia media = new Media(mILibVLC, uri);
play(media);
}
/**
* Starts playback from an already prepared Media
* @param media The {@link Media} to play
* @param media The {@link IMedia} to play
*/
public void play(@NonNull Media media) {
public void play(@NonNull IMedia media) {
setMedia(media);
media.release();
play();
@ -1066,14 +1070,14 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
/**
* Get the current video track
*/
public Media.VideoTrack getCurrentVideoTrack() {
public IMedia.VideoTrack getCurrentVideoTrack() {
if (getVideoTrack() == -1)
return null;
final int trackCount = mMedia.getTrackCount();
for (int i = 0; i < trackCount; ++i) {
final Media.Track track = mMedia.getTrack(i);
if (track.type == Media.Track.Type.Video)
return (Media.VideoTrack) track;
final IMedia.Track track = mMedia.getTrack(i);
if (track.type == IMedia.Track.Type.Video)
return (IMedia.VideoTrack) track;
}
return null;
}
@ -1208,7 +1212,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
/**
* Add a slave (or subtitle) to the current media player.
*
* @param type see {@link org.videolan.libvlc.Media.Slave.Type}
* @param type see {@link IMedia.Slave.Type}
* @param uri a valid RFC 2396 Uri
* @return true on success.
*/
@ -1230,7 +1234,7 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
/**
* Add a slave (or subtitle) to the current media player.
*
* @param type see {@link org.videolan.libvlc.Media.Slave.Type}
* @param type see {@link IMedia.Slave.Type}
* @param path a local path
* @return true on success.
*/
@ -1381,10 +1385,10 @@ public class MediaPlayer extends VLCObject<MediaPlayer.Event> {
}
/* JNI */
private native void nativeNewFromLibVlc(LibVLC libVLC, AWindow window);
private native void nativeNewFromMedia(Media media, AWindow window);
private native void nativeNewFromLibVlc(ILibVLC ILibVLC, AWindow window);
private native void nativeNewFromMedia(IMedia media, AWindow window);
private native void nativeRelease();
private native void nativeSetMedia(Media media);
private native void nativeSetMedia(IMedia media);
private native void nativePlay();
private native void nativeStop();
private native int nativeSetRenderer(RendererItem item);

View File

@ -26,12 +26,15 @@ import java.util.List;
import androidx.annotation.Nullable;
import androidx.collection.LongSparseArray;
import org.videolan.libvlc.interfaces.AbstractVLCEvent;
import org.videolan.libvlc.interfaces.ILibVLC;
public class RendererDiscoverer extends VLCObject<RendererDiscoverer.Event> {
private final static String TAG = "LibVLC/RendererDiscoverer";
final List<RendererItem> mRenderers = new ArrayList<>();
public static class Event extends VLCEvent {
public static class Event extends AbstractVLCEvent {
public static final int ItemAdded = 0x502;
public static final int ItemDeleted = 0x503;
@ -49,7 +52,7 @@ public class RendererDiscoverer extends VLCObject<RendererDiscoverer.Event> {
}
@Override
void release() {
public void release() {
item.release();
super.release();
}
@ -60,17 +63,17 @@ public class RendererDiscoverer extends VLCObject<RendererDiscoverer.Event> {
return new RendererItem(name, type, iconUrl, flags, ref);
}
public interface EventListener extends VLCEvent.Listener<RendererDiscoverer.Event> {}
public interface EventListener extends AbstractVLCEvent.Listener<RendererDiscoverer.Event> {}
/**
* Create a MediaDiscover.
*
* @param libVLC a valid LibVLC
* @param ILibVLC a valid LibVLC
* @param name Name of the vlc service discovery.
*/
public RendererDiscoverer(LibVLC libVLC, String name) {
super(libVLC);
nativeNew(libVLC, name);
public RendererDiscoverer(ILibVLC ILibVLC, String name) {
super(ILibVLC);
nativeNew(ILibVLC, name);
}
/**
@ -98,8 +101,8 @@ public class RendererDiscoverer extends VLCObject<RendererDiscoverer.Event> {
super.setEventListener(listener);
}
public static Description[] list(LibVLC libVlc) {
return nativeList(libVlc);
public static Description[] list(ILibVLC ILibVlc) {
return nativeList(ILibVlc);
}
public static class Description {
@ -154,10 +157,10 @@ public class RendererDiscoverer extends VLCObject<RendererDiscoverer.Event> {
}
/* JNI */
private native void nativeNew(LibVLC libVLC, String name);
private native void nativeNew(ILibVLC ILibVLC, String name);
private native void nativeRelease();
private native boolean nativeStart();
private native void nativeStop();
private static native Description[] nativeList(LibVLC libVLC);
private static native Description[] nativeList(ILibVLC ILibVLC);
private native RendererItem nativeNewItem(long ref);
}

View File

@ -2,6 +2,8 @@ package org.videolan.libvlc;
import androidx.annotation.Nullable;
import org.videolan.libvlc.interfaces.AbstractVLCEvent;
@SuppressWarnings("unused, JniMissingFunction")
public class RendererItem extends VLCObject<RendererItem.Event> {
@ -42,7 +44,7 @@ public class RendererItem extends VLCObject<RendererItem.Event> {
nativeReleaseItem();
}
public static class Event extends VLCEvent {
public static class Event extends AbstractVLCEvent {
protected Event(int type) {
super(type);
}

View File

@ -27,23 +27,27 @@ import java.lang.ref.WeakReference;
import androidx.annotation.Nullable;
import org.videolan.libvlc.interfaces.AbstractVLCEvent;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.interfaces.IVLCObject;
@SuppressWarnings("JniMissingFunction")
abstract class VLCObject<T extends VLCEvent> {
private VLCEvent.Listener<T> mEventListener = null;
abstract class VLCObject<T extends AbstractVLCEvent> implements IVLCObject<T> {
private AbstractVLCEvent.Listener<T> mEventListener = null;
private Handler mHandler = null;
final LibVLC mLibVLC;
final ILibVLC mILibVLC;
private int mNativeRefCount = 1;
protected VLCObject(LibVLC libvlc) {
mLibVLC = libvlc;
protected VLCObject(ILibVLC libvlc) {
mILibVLC = libvlc;
}
protected VLCObject(VLCObject parent) {
mLibVLC = parent.mLibVLC;
protected VLCObject(IVLCObject parent) {
mILibVLC = parent.getLibVLC();
}
protected VLCObject() {
mLibVLC = null;
mILibVLC = null;
}
/**
@ -99,22 +103,27 @@ abstract class VLCObject<T extends VLCEvent> {
throw new AssertionError("VLCObject (" + getClass().getName() + ") finalized but not natively released (" + mNativeRefCount + " refs)");
}
@Override
public ILibVLC getLibVLC() {
return mILibVLC;
}
/**
* Set an event listener.
* Events are sent via the android main thread.
*
* @param listener see {@link VLCEvent.Listener}
* @param listener see {@link AbstractVLCEvent.Listener}
*/
protected synchronized void setEventListener(VLCEvent.Listener<T> listener) {
protected synchronized void setEventListener(AbstractVLCEvent.Listener<T> listener) {
setEventListener(listener, null);
}
/**
* Set an event listener and an executor Handler
* @param listener see {@link VLCEvent.Listener}
* @param listener see {@link AbstractVLCEvent.Listener}
* @param handler Handler in which events are sent. If null, a handler will be created running on the main thread
*/
protected synchronized void setEventListener(VLCEvent.Listener<T> listener, Handler handler) {
protected synchronized void setEventListener(AbstractVLCEvent.Listener<T> listener, Handler handler) {
if (mHandler != null)
mHandler.removeCallbacksAndMessages(null);
mEventListener = listener;
@ -151,10 +160,10 @@ abstract class VLCObject<T extends VLCEvent> {
final T event = onEventNative(eventType, arg1, arg2, argf1, args1);
class EventRunnable implements Runnable {
private final VLCEvent.Listener<T> listener;
private final AbstractVLCEvent.Listener<T> listener;
private final T event;
private EventRunnable(VLCEvent.Listener<T> listener, T event) {
private EventRunnable(AbstractVLCEvent.Listener<T> listener, T event) {
this.listener = listener;
this.event = event;
}
@ -173,7 +182,7 @@ abstract class VLCObject<T extends VLCEvent> {
/* used only before API 7: substitute for NewWeakGlobalRef */
@SuppressWarnings("unused") /* Used from JNI */
private Object getWeakReference() {
return new WeakReference<VLCObject>(this);
return new WeakReference<IVLCObject>(this);
}
@SuppressWarnings("unchecked,unused") /* Used from JNI */
private static void dispatchEventFromWeakNative(Object weak, int eventType, long arg1, long arg2,

View File

@ -15,6 +15,8 @@ import android.view.ViewStub;
import android.widget.FrameLayout;
import org.videolan.R;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.interfaces.IVLCVout;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.libvlc.util.DisplayManager;
import org.videolan.libvlc.util.VLCVideoLayout;
@ -135,11 +137,11 @@ class VideoHelper implements IVLCVout.OnNewVideoLayoutListener {
break;
case SURFACE_FIT_SCREEN:
case SURFACE_FILL: {
Media.VideoTrack vtrack = mMediaPlayer.getCurrentVideoTrack();
IMedia.VideoTrack vtrack = mMediaPlayer.getCurrentVideoTrack();
if (vtrack == null)
return;
final boolean videoSwapped = vtrack.orientation == Media.VideoTrack.Orientation.LeftBottom
|| vtrack.orientation == Media.VideoTrack.Orientation.RightTop;
final boolean videoSwapped = vtrack.orientation == IMedia.VideoTrack.Orientation.LeftBottom
|| vtrack.orientation == IMedia.VideoTrack.Orientation.RightTop;
if (mCurrentScaleType == MediaPlayer.ScaleType.SURFACE_FIT_SCREEN) {
int videoW = vtrack.width;
int videoH = vtrack.height;

View File

@ -18,44 +18,44 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
package org.videolan.libvlc;
package org.videolan.libvlc.interfaces;
import androidx.annotation.Nullable;
public abstract class VLCEvent {
public abstract class AbstractVLCEvent {
public final int type;
protected final long arg1;
protected final long arg2;
protected final float argf1;
protected final String args1;
VLCEvent(int type) {
public AbstractVLCEvent(int type) {
this.type = type;
this.arg1 = this.arg2 = 0;
this.argf1 = 0.0f;
this.args1 = null;
}
VLCEvent(int type, long arg1) {
public AbstractVLCEvent(int type, long arg1) {
this.type = type;
this.arg1 = arg1;
this.arg2 = 0;
this.argf1 = 0.0f;
this.args1 = null;
}
VLCEvent(int type, long arg1, long arg2) {
public AbstractVLCEvent(int type, long arg1, long arg2) {
this.type = type;
this.arg1 = arg1;
this.arg2 = arg2;
this.argf1 = 0.0f;
this.args1 = null;
}
VLCEvent(int type, float argf) {
public AbstractVLCEvent(int type, float argf) {
this.type = type;
this.arg1 = this.arg2 = 0;
this.argf1 = argf;
this.args1 = null;
}
VLCEvent(int type, long arg1, @Nullable String args1) {
public AbstractVLCEvent(int type, long arg1, @Nullable String args1) {
this.type = type;
this.arg1 = arg1;
this.arg2 = 0;
@ -63,16 +63,16 @@ public abstract class VLCEvent {
this.args1 = args1;
}
void release() {
public void release() {
/* do nothing */
}
/**
* Listener for libvlc events
*
* @see VLCEvent
* @see AbstractVLCEvent
*/
public interface Listener<T extends VLCEvent> {
public interface Listener<T extends AbstractVLCEvent> {
void onEvent(T event);
}
}

View File

@ -0,0 +1,5 @@
package org.videolan.libvlc.interfaces;
public interface IComponentFactory {
}

View File

@ -0,0 +1,13 @@
package org.videolan.libvlc.interfaces;
import android.content.Context;
public interface ILibVLC extends IVLCObject<ILibVLC.Event> {
class Event extends AbstractVLCEvent {
protected Event(int type) {
super(type);
}
}
Context getAppContext();
}

View File

@ -0,0 +1,13 @@
package org.videolan.libvlc.interfaces;
import android.content.Context;
import java.util.List;
public interface ILibVLCFactory extends IComponentFactory {
String factoryId = ILibVLCFactory.class.getName();
ILibVLC getFromOptions(Context context, List<String> options);
ILibVLC getFromContext(Context context);
}

View File

@ -0,0 +1,385 @@
package org.videolan.libvlc.interfaces;
import android.net.Uri;
public interface IMedia extends IVLCObject<IMedia.Event> {
class Event extends AbstractVLCEvent {
public static final int MetaChanged = 0;
public static final int SubItemAdded = 1;
public static final int DurationChanged = 2;
public static final int ParsedChanged = 3;
//public static final int Freed = 4;
public static final int StateChanged = 5;
public static final int SubItemTreeAdded = 6;
public Event(int type) {
super(type);
}
public Event(int type, long arg1) {
super(type, arg1);
}
public int getMetaId() {
return (int) arg1;
}
/**
* Get the ParsedStatus in case of {@link Event#ParsedChanged} event
*
* @return {@link ParsedStatus}
*/
public int getParsedStatus() {
return (int) arg1;
}
}
interface EventListener extends AbstractVLCEvent.Listener<Event> {
}
/**
* libvlc_media_type_t
*/
class Type {
public static final int Unknown = 0;
public static final int File = 1;
public static final int Directory = 2;
public static final int Disc = 3;
public static final int Stream = 4;
public static final int Playlist = 5;
}
/**
* see libvlc_meta_t
*/
class Meta {
public static final int Title = 0;
public static final int Artist = 1;
public static final int Genre = 2;
public static final int Copyright = 3;
public static final int Album = 4;
public static final int TrackNumber = 5;
public static final int Description = 6;
public static final int Rating = 7;
public static final int Date = 8;
public static final int Setting = 9;
public static final int URL = 10;
public static final int Language = 11;
public static final int NowPlaying = 12;
public static final int Publisher = 13;
public static final int EncodedBy = 14;
public static final int ArtworkURL = 15;
public static final int TrackID = 16;
public static final int TrackTotal = 17;
public static final int Director = 18;
public static final int Season = 19;
public static final int Episode = 20;
public static final int ShowName = 21;
public static final int Actors = 22;
public static final int AlbumArtist = 23;
public static final int DiscNumber = 24;
public static final int MAX = 25;
}
/**
* see libvlc_state_t
*/
class State {
public static final int NothingSpecial = 0;
public static final int Opening = 1;
/* deprecated public static final int Buffering = 2; */
public static final int Playing = 3;
public static final int Paused = 4;
public static final int Stopped = 5;
public static final int Ended = 6;
public static final int Error = 7;
public static final int MAX = 8;
}
/**
* see libvlc_media_parse_flag_t
*/
class Parse {
public static final int ParseLocal = 0;
public static final int ParseNetwork = 0x01;
public static final int FetchLocal = 0x02;
public static final int FetchNetwork = 0x04;
public static final int DoInteract = 0x08;
}
/*
* see libvlc_media_parsed_status_t
*/
class ParsedStatus {
public static final int Skipped = 1;
public static final int Failed = 2;
public static final int Timeout = 3;
public static final int Done = 4;
}
/**
* see libvlc_media_track_t
*/
abstract class Track {
public static class Type {
public static final int Unknown = -1;
public static final int Audio = 0;
public static final int Video = 1;
public static final int Text = 2;
}
public final int type;
public final String codec;
public final String originalCodec;
public final int id;
public final int profile;
public final int level;
public final int bitrate;
public final String language;
public final String description;
protected Track(int type, String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description) {
this.type = type;
this.codec = codec;
this.originalCodec = originalCodec;
this.id = id;
this.profile = profile;
this.level = level;
this.bitrate = bitrate;
this.language = language;
this.description = description;
}
}
/**
* see libvlc_audio_track_t
*/
class AudioTrack extends Track {
public final int channels;
public final int rate;
public AudioTrack(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description,
int channels, int rate) {
super(Type.Audio, codec, originalCodec, id, profile, level, bitrate, language, description);
this.channels = channels;
this.rate = rate;
}
}
/**
* see libvlc_video_track_t
*/
class VideoTrack extends Track {
public static final class Orientation {
/**
* Top line represents top, left column left
*/
public static final int TopLeft = 0;
/**
* Flipped horizontally
*/
public static final int TopRight = 1;
/**
* Flipped vertically
*/
public static final int BottomLeft = 2;
/**
* Rotated 180 degrees
*/
public static final int BottomRight = 3;
/**
* Transposed
*/
public static final int LeftTop = 4;
/**
* Rotated 90 degrees clockwise (or 270 anti-clockwise)
*/
public static final int LeftBottom = 5;
/**
* Rotated 90 degrees anti-clockwise
*/
public static final int RightTop = 6;
/**
* Anti-transposed
*/
public static final int RightBottom = 7;
}
public static final class Projection {
public static final int Rectangular = 0;
/**
* 360 spherical
*/
public static final int EquiRectangular = 1;
public static final int CubemapLayoutStandard = 0x100;
}
public final int height;
public final int width;
public final int sarNum;
public final int sarDen;
public final int frameRateNum;
public final int frameRateDen;
public final int orientation;
public final int projection;
public VideoTrack(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description,
int height, int width, int sarNum, int sarDen, int frameRateNum, int frameRateDen,
int orientation, int projection) {
super(Type.Video, codec, originalCodec, id, profile, level, bitrate, language, description);
this.height = height;
this.width = width;
this.sarNum = sarNum;
this.sarDen = sarDen;
this.frameRateNum = frameRateNum;
this.frameRateDen = frameRateDen;
this.orientation = orientation;
this.projection = projection;
}
}
/**
* see libvlc_subtitle_track_t
*/
class SubtitleTrack extends Track {
public final String encoding;
public SubtitleTrack(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description,
String encoding) {
super(Type.Text, codec, originalCodec, id, profile, level, bitrate, language, description);
this.encoding = encoding;
}
}
/**
* see libvlc_subtitle_track_t
*/
class UnknownTrack extends Track {
public UnknownTrack(String codec, String originalCodec, int id, int profile,
int level, int bitrate, String language, String description) {
super(Type.Unknown, codec, originalCodec, id, profile, level, bitrate, language, description);
}
}
/**
* see libvlc_media_slave_t
*/
class Slave {
public static class Type {
public static final int Subtitle = 0;
public static final int Audio = 1;
}
/**
* @see Type
*/
public final int type;
/**
* From 0 (low priority) to 4 (high priority)
*/
public final int priority;
public final String uri;
public Slave(int type, int priority, String uri) {
this.type = type;
this.priority = priority;
this.uri = uri;
}
}
/**
* see libvlc_media_stats_t
*/
class Stats {
public final int readBytes;
public final float inputBitrate;
public final int demuxReadBytes;
public final float demuxBitrate;
public final int demuxCorrupted;
public final int demuxDiscontinuity;
public final int decodedVideo;
public final int decodedAudio;
public final int displayedPictures;
public final int lostPictures;
public final int playedAbuffers;
public final int lostAbuffers;
public final int sentPackets;
public final int sentBytes;
public final float sendBitrate;
public Stats(int readBytes, float inputBitrate, int demuxReadBytes,
float demuxBitrate, int demuxCorrupted,
int demuxDiscontinuity, int decodedVideo, int decodedAudio,
int displayedPictures, int lostPictures, int playedAbuffers,
int lostAbuffers, int sentPackets, int sentBytes,
float sendBitrate) {
this.readBytes = readBytes;
this.inputBitrate = inputBitrate;
this.demuxReadBytes = demuxReadBytes;
this.demuxBitrate = demuxBitrate;
this.demuxCorrupted = demuxCorrupted;
this.demuxDiscontinuity = demuxDiscontinuity;
this.decodedVideo = decodedVideo;
this.decodedAudio = decodedAudio;
this.displayedPictures = displayedPictures;
this.lostPictures = lostPictures;
this.playedAbuffers = playedAbuffers;
this.lostAbuffers = lostAbuffers;
this.sentPackets = sentPackets;
this.sentBytes = sentBytes;
this.sendBitrate = sendBitrate;
}
}
long getDuration();
int getState();
IMediaList subItems();
boolean parse(int flags);
boolean parse();
boolean parseAsync(int flags, int timeout);
boolean parseAsync(int flags);
boolean parseAsync();
int getType();
int getTrackCount();
Track getTrack(int idx);
String getMeta(int id);
void setHWDecoderEnabled(boolean enabled, boolean force);
void setEventListener(EventListener listener);
void addOption(String option);
void addSlave(Slave slave);
void clearSlaves();
Slave[] getSlaves();
Uri getUri();
boolean isParsed();
Stats getStats();
/**
* Enable HWDecoder options if not already set
*/
void setDefaultMediaPlayerOptions();
}

View File

@ -0,0 +1,15 @@
package org.videolan.libvlc.interfaces;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import java.io.FileDescriptor;
public interface IMediaFactory extends IComponentFactory {
String factoryId = IMediaFactory.class.getName();
IMedia getFromLocalPath(ILibVLC ILibVLC, String path);
IMedia getFromUri(ILibVLC ILibVLC, Uri uri);
IMedia getFromFileDescriptor(ILibVLC ILibVLC, FileDescriptor fd);
IMedia getFromAssetFileDescriptor(ILibVLC ILibVLC, AssetFileDescriptor assetFileDescriptor);
}

View File

@ -0,0 +1,57 @@
package org.videolan.libvlc.interfaces;
import android.os.Handler;
public interface IMediaList extends IVLCObject<IMediaList.Event> {
class Event extends AbstractVLCEvent {
public static final int ItemAdded = 0x200;
//public static final int WillAddItem = 0x201;
public static final int ItemDeleted = 0x202;
//public static final int WillDeleteItem = 0x203;
public static final int EndReached = 0x204;
/**
* In case of ItemDeleted, the media will be already released. If it's released, cached
* attributes are still available (like {@link IMedia#getUri()}}).
*/
public final IMedia media;
private final boolean retain;
public final int index;
public Event(int type, IMedia media, boolean retain, int index) {
super(type);
if (retain && (media == null || !media.retain()))
throw new IllegalStateException("invalid media reference");
this.media = media;
this.retain = retain;
this.index = index;
}
@Override
public void release() {
if (retain)
media.release();
}
}
interface EventListener extends AbstractVLCEvent.Listener<IMediaList.Event> {
}
void setEventListener(EventListener listener, Handler handler);
/**
* Get the number of Media.
*/
int getCount();
/**
* Get a Media at specified index.
*
* @param index index of the media
* @return Media hold by MediaList. This Media should be released with {@link #release()}.
*/
IMedia getMediaAt(int index);
boolean isLocked();
}

View File

@ -0,0 +1,11 @@
package org.videolan.libvlc.interfaces;
public interface IVLCObject<T extends AbstractVLCEvent> {
boolean retain();
void release();
boolean isReleased();
ILibVLC getLibVLC();
}

View File

@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
package org.videolan.libvlc;
package org.videolan.libvlc.interfaces;
import android.annotation.TargetApi;
import android.graphics.SurfaceTexture;

View File

@ -32,10 +32,11 @@ import android.os.Parcelable;
import android.view.Surface;
import android.view.SurfaceHolder;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.Media;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Map;
@ -67,13 +68,13 @@ public class MediaPlayer
public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1;
public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2;
private Media mCurrentMedia = null;
private final LibVLC mLibVLC;
private IMedia mCurrentMedia = null;
private final ILibVLC mILibVLC;
private org.videolan.libvlc.MediaPlayer mMediaPlayer;
public MediaPlayer() {
mLibVLC = new LibVLC(null); //FIXME, this is wrong
mMediaPlayer = new org.videolan.libvlc.MediaPlayer(mLibVLC);
mILibVLC = new LibVLC(null); //FIXME, this is wrong
mMediaPlayer = new org.videolan.libvlc.MediaPlayer(mILibVLC);
}
public static MediaPlayer create(Context context, Uri uri) {
@ -108,19 +109,19 @@ public class MediaPlayer
// FIXME, this is INCORRECT, @headers are ignored
public void setDataSource(Context context, Uri uri, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
mCurrentMedia = new Media(mLibVLC, uri);
mCurrentMedia = new Media(mILibVLC, uri);
mMediaPlayer.setMedia(mCurrentMedia);
}
public void setDataSource(String path)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
mCurrentMedia = new Media(mLibVLC, path);
mCurrentMedia = new Media(mILibVLC, path);
mMediaPlayer.setMedia(mCurrentMedia);
}
public void setDataSource(FileDescriptor fd)
throws IOException, IllegalArgumentException, IllegalStateException {
mCurrentMedia = new Media(mLibVLC, fd);
mCurrentMedia = new Media(mILibVLC, fd);
mMediaPlayer.setMedia(mCurrentMedia);
}
@ -279,11 +280,11 @@ public class MediaPlayer
public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
public void addTimedTextSource(String path, String mimeType) {
mMediaPlayer.addSlave(Media.Slave.Type.Subtitle, path, false);
mMediaPlayer.addSlave(IMedia.Slave.Type.Subtitle, path, false);
}
public void addTimedTextSource(Context context, Uri uri, String mimeType) {
mMediaPlayer.addSlave(Media.Slave.Type.Subtitle, uri, false);
mMediaPlayer.addSlave(IMedia.Slave.Type.Subtitle, uri, false);
}
public void addTimedTextSource(FileDescriptor fd, String mimeType)

View File

@ -42,7 +42,9 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.MediaController;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.Media;
import java.io.InputStream;
@ -51,11 +53,11 @@ import java.util.Map;
public class VideoView extends SurfaceView
implements MediaController.MediaPlayerControl {
private static LibVLC sLibVLC;
private static ILibVLC sILibVLC;
public VideoView(Context context) {
super(context);
sLibVLC = new LibVLC(context, null);
sILibVLC = new LibVLC(context, null);
}
public VideoView(Context context, AttributeSet attrs) {
@ -88,11 +90,11 @@ public class VideoView extends SurfaceView
}
public void setVideoPath(String path) {
final Media media = new Media(sLibVLC, path);
final IMedia media = new Media(sILibVLC, path);
}
public void setVideoURI(Uri uri) {
final Media media = new Media(sLibVLC, uri);
final IMedia media = new Media(sILibVLC, uri);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)

View File

@ -0,0 +1,24 @@
package org.videolan.libvlc.stubs;
import android.content.Context;
import org.videolan.libvlc.interfaces.ILibVLC;
import java.util.List;
public class StubLibVLC extends StubVLCObject<ILibVLC.Event> implements ILibVLC {
private final Context mContext;
public StubLibVLC(Context context, List<String> options) {
this.mContext = context;
}
public StubLibVLC(Context context) {
this(context, null);
}
@Override
public Context getAppContext() {
return mContext;
}
}

View File

@ -0,0 +1,20 @@
package org.videolan.libvlc.stubs;
import android.content.Context;
import org.videolan.libvlc.interfaces.ILibVLCFactory;
import org.videolan.libvlc.interfaces.ILibVLC;
import java.util.List;
public class StubLibVLCFactory implements ILibVLCFactory {
@Override
public ILibVLC getFromOptions(Context context, List<String> options) {
return new StubLibVLC(context, options);
}
@Override
public ILibVLC getFromContext(Context context) {
return new StubLibVLC(context);
}
}

View File

@ -0,0 +1,183 @@
package org.videolan.libvlc.stubs;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.interfaces.IMediaList;
import java.io.FileDescriptor;
public class StubMedia extends StubVLCObject<IMedia.Event> implements IMedia {
private Uri mUri;
private ILibVLC mILibVLC;
private int mType = Type.Unknown;
public StubMedia(ILibVLC ILibVLC, String path) {
this(ILibVLC, Uri.parse(path));
}
public StubMedia(ILibVLC ILibVLC, Uri uri) {
mUri = uri;
mILibVLC = ILibVLC;
}
public StubMedia(ILibVLC ILibVLC, FileDescriptor fd) {
mILibVLC = ILibVLC;
}
public StubMedia(ILibVLC ILibVLC, AssetFileDescriptor assetFileDescriptor) {
mILibVLC = ILibVLC;
}
@Override
public long getDuration() {
return 0;
}
@Override
public int getState() {
return 0;
}
@Override
public IMediaList subItems() {
return new StubMediaList();
}
@Override
public boolean parse(int flags) {
return false;
}
@Override
public boolean parse() {
return false;
}
@Override
public boolean parseAsync(int flags, int timeout) {
return false;
}
@Override
public boolean parseAsync(int flags) {
return false;
}
@Override
public boolean parseAsync() {
return false;
}
@Override
public int getType() {
return mType;
}
@Override
public int getTrackCount() {
return 0;
}
@Override
public Track getTrack(int idx) {
return null;
}
@Override
public String getMeta(int id) {
if (mUri == null)
return null;
switch (id) {
case Meta.Title:
return getTitle();
case Meta.URL:
return mUri.getPath();
}
return null;
}
private String getTitle() {
if ("file".equals(mUri.getScheme())) {
return mUri.getLastPathSegment();
}
return mUri.getPath();
}
@Override
public void setHWDecoderEnabled(boolean enabled, boolean force) {
}
@Override
public void setEventListener(EventListener listener) {
}
@Override
public void addOption(String option) {
}
@Override
public void addSlave(Slave slave) {
}
@Override
public void clearSlaves() {
}
@Override
public Slave[] getSlaves() {
return new Slave[0];
}
@Override
public Uri getUri() {
return mUri;
}
@Override
public boolean isParsed() {
return false;
}
@Override
public Stats getStats() {
return null;
}
@Override
public void setDefaultMediaPlayerOptions() {
}
@Override
public boolean retain() {
return false;
}
@Override
public void release() {
}
@Override
public boolean isReleased() {
return false;
}
@Override
public ILibVLC getLibVLC() {
return mILibVLC;
}
public void setType(int type) {
this.mType = type;
}
}

View File

@ -0,0 +1,32 @@
package org.videolan.libvlc.stubs;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import org.videolan.libvlc.interfaces.IMediaFactory;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.interfaces.IMedia;
import java.io.FileDescriptor;
public class StubMediaFactory implements IMediaFactory {
@Override
public IMedia getFromLocalPath(ILibVLC ILibVLC, String path) {
return new StubMedia(ILibVLC, path);
}
@Override
public IMedia getFromUri(ILibVLC ILibVLC, Uri uri) {
return new StubMedia(ILibVLC, uri);
}
@Override
public IMedia getFromFileDescriptor(ILibVLC ILibVLC, FileDescriptor fd) {
return new StubMedia(ILibVLC, fd);
}
@Override
public IMedia getFromAssetFileDescriptor(ILibVLC ILibVLC, AssetFileDescriptor assetFileDescriptor) {
return new StubMedia(ILibVLC, assetFileDescriptor);
}
}

View File

@ -0,0 +1,28 @@
package org.videolan.libvlc.stubs;
import android.os.Handler;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.interfaces.IMediaList;
public class StubMediaList extends StubVLCObject<IMediaList.Event> implements IMediaList {
@Override
public void setEventListener(EventListener listener, Handler handler) {
}
@Override
public int getCount() {
return 0;
}
@Override
public IMedia getMediaAt(int index) {
return null;
}
@Override
public boolean isLocked() {
return false;
}
}

View File

@ -0,0 +1,27 @@
package org.videolan.libvlc.stubs;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.interfaces.AbstractVLCEvent;
import org.videolan.libvlc.interfaces.IVLCObject;
public class StubVLCObject<T extends AbstractVLCEvent> implements IVLCObject<T> {
@Override
public boolean retain() {
return false;
}
@Override
public void release() {
}
@Override
public boolean isReleased() {
return false;
}
@Override
public ILibVLC getLibVLC() {
return null;
}
}

View File

@ -23,7 +23,9 @@ package org.videolan.libvlc.util;
import android.net.Uri;
import androidx.annotation.MainThread;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;
@ -35,7 +37,7 @@ public class Dumper {
void onProgress(float progress);
}
private final LibVLC mLibVLC;
private final ILibVLC mILibVLC;
private final MediaPlayer mMediaPlayer;
private final Listener mListener;
@ -60,9 +62,9 @@ public class Dumper {
options.add("--no-audio");
options.add("--no-spu");
options.add("-vv");
mLibVLC = new LibVLC(null, options);
mILibVLC = new LibVLC(null, options);
final Media media = new Media(mLibVLC, uri);
final IMedia media = new Media(mILibVLC, uri);
mMediaPlayer = new MediaPlayer(media);
mMediaPlayer.setEventListener(new MediaPlayer.EventListener() {
@Override
@ -99,6 +101,6 @@ public class Dumper {
public void cancel() {
mMediaPlayer.stop();
mMediaPlayer.release();
mLibVLC.release();
mILibVLC.release();
}
}

View File

@ -25,9 +25,12 @@ import android.os.Handler;
import androidx.annotation.MainThread;
import android.util.Log;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.FactoryManager;
import org.videolan.libvlc.interfaces.IMediaFactory;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.MediaDiscoverer;
import org.videolan.libvlc.interfaces.IMediaList;
import org.videolan.libvlc.MediaList;
import java.util.ArrayList;
@ -35,14 +38,15 @@ import java.util.ArrayList;
public class MediaBrowser {
private static final String TAG = "MediaBrowser";
private final LibVLC mLibVlc;
private final ILibVLC mILibVlc;
private final ArrayList<MediaDiscoverer> mMediaDiscoverers = new ArrayList<MediaDiscoverer>();
private final ArrayList<Media> mDiscovererMediaArray = new ArrayList<Media>();
private MediaList mBrowserMediaList;
private Media mMedia;
private final ArrayList<IMedia> mDiscovererMediaArray = new ArrayList<IMedia>();
private IMediaList mBrowserMediaList;
private IMedia mMedia;
private EventListener mEventListener;
private Handler mHandler;
private boolean mAlive;
private IMediaFactory mFactory;
private static final String IGNORE_LIST_OPTION = ":ignore-filetypes=";
private String mIgnoreList = "db,nfo,ini,jpg,jpeg,ljpg,gif,png,pgm,pgmyuv,pbm,pam,tga,bmp,pnm,xpm,xcf,pcx,tif,tiff,lbm,sfv,txt,sub,idx,srt,ssa,ass,smi,utf,utf-8,rt,aqt,txt,usf,jss,cdg,psb,mpsub,mpl2,pjs,dks,stl,vtt,ttml";
@ -65,14 +69,14 @@ public class MediaBrowser {
* @param index
* @param media
*/
void onMediaAdded(int index, Media media);
void onMediaAdded(int index, IMedia media);
/**
* Received when a media is removed (Happens only when you discover networks)
* @param index
* @param media Released media, but cached attributes are still
* available (like media.getMrl())
*/
void onMediaRemoved(int index, Media media);
void onMediaRemoved(int index, IMedia media);
/**
* Called when browse ended.
* It won't be called when you discover networks
@ -87,9 +91,10 @@ public class MediaBrowser {
*
* With this constructor, callbacks will be executed in the main thread
*/
public MediaBrowser(LibVLC libvlc, EventListener listener) {
mLibVlc = libvlc;
mLibVlc.retain();
public MediaBrowser(ILibVLC libvlc, EventListener listener) {
mFactory = ((IMediaFactory) FactoryManager.getFactory(IMediaFactory.factoryId));
mILibVlc = libvlc;
mILibVlc.retain();
mEventListener = listener;
mAlive = true;
}
@ -100,7 +105,7 @@ public class MediaBrowser {
* @param listener The Listener which will receive callbacks
* @param handler Optional Handler in which callbacks will be posted. If set to null, a Handler will be created running on the main thread
*/
public MediaBrowser(LibVLC libvlc, EventListener listener, Handler handler) {
public MediaBrowser(ILibVLC libvlc, EventListener listener, Handler handler) {
this(libvlc, listener);
mHandler = handler;
}
@ -129,7 +134,7 @@ public class MediaBrowser {
reset();
if (!mAlive)
throw new IllegalStateException("MediaBrowser released more than one time");
mLibVlc.release();
mILibVlc.release();
mAlive = false;
}
@ -144,7 +149,7 @@ public class MediaBrowser {
}
private void startMediaDiscoverer(String discovererName) {
MediaDiscoverer md = new MediaDiscoverer(mLibVlc, discovererName);
MediaDiscoverer md = new MediaDiscoverer(mILibVlc, discovererName);
mMediaDiscoverers.add(md);
final MediaList ml = md.getMediaList();
ml.setEventListener(mDiscovererMediaListEventListener, mHandler);
@ -160,7 +165,7 @@ public class MediaBrowser {
reset();
final MediaDiscoverer.Description descriptions[] =
MediaDiscoverer.list(mLibVlc, MediaDiscoverer.Description.Category.Lan);
MediaDiscoverer.list(mILibVlc, MediaDiscoverer.Description.Category.Lan);
if (descriptions == null)
return;
for (MediaDiscoverer.Description description : descriptions) {
@ -187,7 +192,7 @@ public class MediaBrowser {
*/
@MainThread
public void browse(String path, int flags) {
final Media media = new Media(mLibVlc, path);
final IMedia media = mFactory.getFromLocalPath(mILibVlc, path);
browse(media, flags);
media.release();
}
@ -200,7 +205,7 @@ public class MediaBrowser {
*/
@MainThread
public void browse(Uri uri, int flags) {
final Media media = new Media(mLibVlc, uri);
final IMedia media = mFactory.getFromUri(mILibVlc, uri);
browse(media, flags);
media.release();
}
@ -212,7 +217,7 @@ public class MediaBrowser {
* @param flags see {@link MediaBrowser.Flag}
*/
@MainThread
public void browse(Media media, int flags) {
public void browse(IMedia media, int flags) {
/* media can be associated with a medialist,
* so increment ref count in order to don't clean it with the medialist
*/
@ -222,9 +227,9 @@ public class MediaBrowser {
media.addOption(":no-sub-autodetect-file");
if ((flags & Flag.ShowHiddenFiles) != 0)
media.addOption(":show-hiddenfiles");
int mediaFlags = Media.Parse.ParseNetwork;
int mediaFlags = IMedia.Parse.ParseNetwork;
if ((flags & Flag.Interact) != 0)
mediaFlags |= Media.Parse.DoInteract;
mediaFlags |= IMedia.Parse.DoInteract;
reset();
mBrowserMediaList = media.subItems();
mBrowserMediaList.setEventListener(mBrowserMediaListEventListener, mHandler);
@ -244,10 +249,10 @@ public class MediaBrowser {
* Get a media at a specified index. Should be released with {@link #release()}.
*/
@MainThread
public Media getMediaAt(int index) {
public IMedia getMediaAt(int index) {
if (index < 0 || index >= getMediaCount())
throw new IndexOutOfBoundsException();
final Media media = mBrowserMediaList != null ? mBrowserMediaList.getMediaAt(index) :
final IMedia media = mBrowserMediaList != null ? mBrowserMediaList.getMediaAt(index) :
mDiscovererMediaArray.get(index);
media.retain();
return media;

View File

@ -28,13 +28,13 @@ import android.os.Build;
import androidx.annotation.NonNull;
import android.util.Log;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.interfaces.ILibVLC;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.Media;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.RandomAccessFile;
@ -552,6 +552,7 @@ public class VLCUtil {
return sb.toString();
}
private static void close(Closeable closeable) {
if (closeable != null)
try {

View File

@ -5,6 +5,7 @@ import android.net.Uri;
import android.os.Parcel;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.medialibrary.interfaces.AbstractMedialibrary;
import org.videolan.medialibrary.interfaces.media.AbstractAlbum;
import org.videolan.medialibrary.interfaces.media.AbstractArtist;
@ -96,7 +97,7 @@ public class MLServiceLocator {
}
}
public static AbstractMediaWrapper getAbstractMediaWrapper(Media media) {
public static AbstractMediaWrapper getAbstractMediaWrapper(IMedia media) {
if (sMode == LocatorMode.VLC_ANDROID) {
return new MediaWrapper(media);
} else {

View File

@ -1,5 +1,4 @@
/*
*****************************************************************************
/*****************************************************************************
* Medialibrary.java
*****************************************************************************
* Copyright © 2017-2018 VLC authors and VideoLAN
@ -59,8 +58,7 @@ public class Medialibrary extends AbstractMedialibrary {
try {
System.loadLibrary("c++_shared");
System.loadLibrary("mla");
} catch (UnsatisfiedLinkError ule)
{
} catch (UnsatisfiedLinkError ule) {
Log.e(TAG, "Can't load mla: " + ule);
return ML_INIT_FAILED;
}
@ -87,10 +85,12 @@ public class Medialibrary extends AbstractMedialibrary {
Log.e(TAG, "Medialib database is corrupted. Clearing it and try to restore playlists");
nativeClearDatabase(true);
}
mIsInitiated = initCode != ML_INIT_FAILED;
return initCode;
}
@Override
public void start() {
if (isStarted()) return;
nativeStart();

View File

@ -10,6 +10,7 @@ import androidx.annotation.Nullable;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.libvlc.util.Extensions;
import org.videolan.libvlc.util.VLCUtil;
import org.videolan.medialibrary.MLServiceLocator;
@ -100,7 +101,7 @@ public abstract class AbstractMediaWrapper extends MediaLibraryItem implements P
protected boolean mIsPictureParsed;
protected int mFlags = 0;
protected long mLastModified = 0L;
protected Media.Slave[] mSlaves = null;
protected IMedia.Slave[] mSlaves = null;
protected long mSeen = 0L;
@ -184,7 +185,7 @@ public abstract class AbstractMediaWrapper extends MediaLibraryItem implements P
*
* @param media should be parsed and not NULL
*/
public AbstractMediaWrapper(Media media) {
public AbstractMediaWrapper(IMedia media) {
super();
if (media == null)
throw new NullPointerException("media was null");
@ -208,7 +209,7 @@ public abstract class AbstractMediaWrapper extends MediaLibraryItem implements P
return !(mUri == null || otherUri == null) && (mUri == otherUri || mUri.equals(otherUri));
}
private void init(Media media) {
private void init(IMedia media) {
mType = TYPE_ALL;
if (media != null) {
@ -216,11 +217,11 @@ public abstract class AbstractMediaWrapper extends MediaLibraryItem implements P
mLength = media.getDuration();
for (int i = 0; i < media.getTrackCount(); ++i) {
final Media.Track track = media.getTrack(i);
final IMedia.Track track = media.getTrack(i);
if (track == null)
continue;
if (track.type == Media.Track.Type.Video) {
final Media.VideoTrack videoTrack = (Media.VideoTrack) track;
final IMedia.VideoTrack videoTrack = (IMedia.VideoTrack) track;
mType = TYPE_VIDEO;
mWidth = videoTrack.width;
mHeight = videoTrack.height;
@ -278,7 +279,7 @@ public abstract class AbstractMediaWrapper extends MediaLibraryItem implements P
private void init(long time, long length, int type,
Bitmap picture, String title, String artist, String genre, String album, String albumArtist,
int width, int height, String artworkURL, int audio, int spu, int trackNumber, int discNumber, long lastModified,
long seen, Media.Slave[] slaves) {
long seen, IMedia.Slave[] slaves) {
mFilename = null;
mTime = time;
mDisplayTime = time;
@ -339,12 +340,12 @@ public abstract class AbstractMediaWrapper extends MediaLibraryItem implements P
return mUri;
}
private static String getMetaId(Media media, String defaultMeta, int id, boolean trim) {
private static String getMetaId(IMedia media, String defaultMeta, int id, boolean trim) {
String meta = media.getMeta(id);
return meta != null ? trim ? meta.trim() : meta : defaultMeta;
}
private void updateMeta(Media media) {
private void updateMeta(IMedia media) {
mTitle = getMetaId(media, mTitle, Media.Meta.Title, true);
mArtist = getMetaId(media, mArtist, Media.Meta.Artist, true);
mAlbum = getMetaId(media, mAlbum, Media.Meta.Album, true);
@ -369,7 +370,7 @@ public abstract class AbstractMediaWrapper extends MediaLibraryItem implements P
public void updateMeta(MediaPlayer mediaPlayer) {
if (!TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mDisplayTitle))
mDisplayTitle = mTitle;
final Media media = mediaPlayer.getMedia();
final IMedia media = mediaPlayer.getMedia();
if (media == null)
return;
updateMeta(media);
@ -636,7 +637,7 @@ public abstract class AbstractMediaWrapper extends MediaLibraryItem implements P
}
@Nullable
public Media.Slave[] getSlaves() {
public IMedia.Slave[] getSlaves() {
return mSlaves;
}
@ -714,9 +715,9 @@ public abstract class AbstractMediaWrapper extends MediaLibraryItem implements P
}
};
protected static class PSlave extends Media.Slave implements Parcelable {
protected static class PSlave extends IMedia.Slave implements Parcelable {
PSlave(Media.Slave slave) {
PSlave(IMedia.Slave slave) {
super(slave.type, slave.priority, slave.uri);
}

View File

@ -28,6 +28,7 @@ import android.os.Parcel;
import android.text.TextUtils;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.medialibrary.Tools;
import org.videolan.medialibrary.interfaces.AbstractMedialibrary;
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper;
@ -57,7 +58,7 @@ public class MediaWrapper extends AbstractMediaWrapper {
}
public MediaWrapper(Uri uri) { super(uri); }
public MediaWrapper(Media media) { super(media); }
public MediaWrapper(IMedia media) { super(media); }
public MediaWrapper(Parcel in) { super(in); }
public void rename(String name) {

View File

@ -1,7 +1,10 @@
package org.videolan.medialibrary.stubs;
import android.os.Environment;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import org.videolan.medialibrary.MLServiceLocator;
import org.videolan.medialibrary.interfaces.media.AbstractAlbum;
import org.videolan.medialibrary.interfaces.media.AbstractArtist;
@ -16,6 +19,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import static org.videolan.medialibrary.interfaces.AbstractMedialibrary.SORT_ALBUM;
import static org.videolan.medialibrary.interfaces.AbstractMedialibrary.SORT_ALPHA;
@ -41,7 +45,9 @@ public class StubDataSource {
ArrayList<AbstractFolder> mFolders = new ArrayList<>();
ArrayList<String> mDevices = new ArrayList<>();
private static long uuid = 2;
private static String baseMrl = Environment.getExternalStorageDirectory().getAbsolutePath() + "/";
private static AtomicLong uuid = new AtomicLong(2);
private static StubDataSource mInstance = null;
@ -52,14 +58,35 @@ public class StubDataSource {
return mInstance;
}
private StubDataSource() { }
private StubDataSource() {
}
public void init() {
String baseMrl = "/storage/emulated/0/Movies/";
}
public void resetData() {
mFolders.clear();
mVideoMediaWrappers.clear();
mAudioMediaWrappers.clear();
}
public void setVideoByCount(int count, @Nullable String folder) {
mVideoMediaWrappers.clear();
AbstractMediaWrapper media;
String fileName;
for (int i = 0; i < count; i++) {
fileName = i + "58_foar_everywun_frum_boxxy.flv";
String mrl = baseMrl + ((folder != null) ? folder + "/" : "") + fileName;
media = MLServiceLocator.getAbstractMediaWrapper(getUUID(), mrl, 0L, 18820L, AbstractMediaWrapper.TYPE_VIDEO,
fileName, fileName, "", "",
"", "", 416, 304, "", 0, -2,
0, 0, 1509466228L, 0L, true, 0);
addVideo(media);
}
// Video
String fileName = "058_foar_everywun_frum_boxxy.flv";
fileName = "058_foar_everywun_frum_boxxy.flv";
media = MLServiceLocator.getAbstractMediaWrapper(getUUID(), baseMrl + fileName, 0L, 18820L, 0,
fileName, fileName, "", "",
"", "", 416, 304, "", 0, -2,
@ -113,9 +140,43 @@ public class StubDataSource {
addAudio(media, "", 1970, 360);
}
public void setAudioByCount(int count, @Nullable String folder) {
mAudioMediaWrappers.clear();
String fileName;
AbstractMediaWrapper media;
for (int i = 0; i < count; i++) {
fileName = i + "-Show Me The Way.mp3";
String mrl = baseMrl + ((folder != null) ? folder + "/" : "") + fileName;
media = MLServiceLocator.getAbstractMediaWrapper(getUUID(), mrl, 0L, 280244L, AbstractMediaWrapper.TYPE_AUDIO,
i + "-Show Me The Way", fileName, "Peter Frampton", "Rock",
"Shine On CD2", "Peter Frampton",
0, 0, baseMrl + folder + ".jpg",
0, -2, 1, 0,
1547452796L, 0L, true, 0);
addAudio(media, "", 1965, 400);
}
}
public AbstractMediaWrapper addMediaWrapper(String mrl, String title, int type) {
AbstractMediaWrapper media = MLServiceLocator.getAbstractMediaWrapper(getUUID(), mrl, 0L, 280224L, type,
title, title, "Artisto", "Jazz", "XYZ CD1", "", 0, 0, baseMrl + title, -2,
1, 1, 0, 1547452796L, 0L, true, 0);
if (type == AbstractMediaWrapper.TYPE_ALL) type = media.getType();
if (type == AbstractMediaWrapper.TYPE_VIDEO) addVideo(media);
else if (type == AbstractMediaWrapper.TYPE_AUDIO) addAudio(media, "", 2018, 12313);
return media;
}
public AbstractFolder createFolder(String name) {
AbstractFolder folder = MLServiceLocator.getAbstractFolder(getUUID(), name, baseMrl + name);
mFolders.add(folder);
return folder;
}
<T> List<T> secureSublist(List<T> list, int offset, int nbItems) {
int min = list.size() - 1 < 0 ? 0 : list.size();
int secureOffset = (offset >= list.size()) && (offset > 0) ? min : offset;
int secureOffset = (offset >= list.size()) && (offset > 0) ? min : offset;
int end = offset + nbItems;
int secureEnd = (end >= list.size()) && end > 0 ? min : end;
return list.subList(secureOffset, secureEnd);
@ -123,40 +184,58 @@ public class StubDataSource {
class MediaComparator implements Comparator<AbstractMediaWrapper> {
private int sort;
MediaComparator(int sort) { this.sort = sort; }
MediaComparator(int sort) {
this.sort = sort;
}
@Override //TODO checkout if types of sort are verified before being used in native
public int compare(AbstractMediaWrapper o1, AbstractMediaWrapper o2) {
switch(sort) {
switch (sort) {
case SORT_DEFAULT:
case SORT_ALPHA: return o1.getTitle().compareTo(o2.getTitle());
case SORT_FILENAME :return o1.getFileName().compareTo(o2.getFileName());
case SORT_DURATION: return (int)(o1.getLength() - o2.getLength());
case SORT_INSERTIONDATE: return (int)(o1.getTime() - o2.getTime()); // TODO checkout if insertiton <=> time
case SORT_LASTMODIFICATIONDATE: return (int)(o1.getLastModified() - o2.getLastModified());
case SORT_ARTIST: return o1.getArtist().compareTo(o2.getArtist());
default: return 0;
case SORT_ALPHA:
return o1.getTitle().compareTo(o2.getTitle());
case SORT_FILENAME:
return o1.getFileName().compareTo(o2.getFileName());
case SORT_DURATION:
return (int) (o1.getLength() - o2.getLength());
case SORT_INSERTIONDATE:
return (int) (o1.getTime() - o2.getTime()); // TODO checkout if insertiton <=> time
case SORT_LASTMODIFICATIONDATE:
return (int) (o1.getLastModified() - o2.getLastModified());
case SORT_ARTIST:
return o1.getArtist().compareTo(o2.getArtist());
default:
return 0;
}
}
}
class ArtistComparator implements Comparator<AbstractArtist> {
private int sort;
ArtistComparator(int sort) { this.sort = sort; }
ArtistComparator(int sort) {
this.sort = sort;
}
@Override
public int compare(AbstractArtist o1, AbstractArtist o2) {
switch(sort) {
switch (sort) {
case SORT_DEFAULT:
case SORT_ARTIST: return o1.getTitle().compareTo(o2.getTitle());
default: return 0;
case SORT_ARTIST:
return o1.getTitle().compareTo(o2.getTitle());
default:
return 0;
}
}
}
class AlbumComparator implements Comparator<AbstractAlbum> {
private int sort;
AlbumComparator(int sort) { this.sort = sort; }
AlbumComparator(int sort) {
this.sort = sort;
}
@Override
public int compare(AbstractAlbum o1, AbstractAlbum o2) {
@ -172,43 +251,59 @@ public class StubDataSource {
class GenreComparator implements Comparator<AbstractGenre> {
private int sort;
GenreComparator(int sort) { this.sort = sort; }
GenreComparator(int sort) {
this.sort = sort;
}
@Override
public int compare(AbstractGenre o1, AbstractGenre o2) {
switch(sort) {
switch (sort) {
case SORT_DEFAULT:
case SORT_ALPHA: return o1.getTitle().compareTo(o2.getTitle());
default: return 0;
case SORT_ALPHA:
return o1.getTitle().compareTo(o2.getTitle());
default:
return 0;
}
}
}
class PlaylistComparator implements Comparator<AbstractPlaylist> {
private int sort;
PlaylistComparator(int sort) { this.sort = sort; }
PlaylistComparator(int sort) {
this.sort = sort;
}
@Override
public int compare(AbstractPlaylist o1, AbstractPlaylist o2) {
switch (sort) {
case SORT_DEFAULT:
case SORT_ALPHA: return o1.getTitle().compareTo(o2.getTitle());
case SORT_DURATION: return 0; //TODO WTF is there a duration attribute
default: return 0;
case SORT_ALPHA:
return o1.getTitle().compareTo(o2.getTitle());
case SORT_DURATION:
return 0; //TODO WTF is there a duration attribute
default:
return 0;
}
}
}
class FolderComparator implements Comparator<AbstractFolder> {
private int sort;
FolderComparator(int sort) { this.sort = sort; }
FolderComparator(int sort) {
this.sort = sort;
}
@Override
public int compare(AbstractFolder o1, AbstractFolder o2) {
switch (sort) {
case SORT_DEFAULT:
case SORT_ALPHA: return o1.getTitle().compareTo(o2.getTitle());
default: return 0;
case SORT_ALPHA:
return o1.getTitle().compareTo(o2.getTitle());
default:
return 0;
}
}
}
@ -291,9 +386,8 @@ public class StubDataSource {
return false;
}
long getUUID() {
uuid++;
return uuid;
public long getUUID() {
return uuid.addAndGet(1);
}
private void addAudio(AbstractMediaWrapper media, String shortBio, int releaseYear, int albumDuration) {
@ -328,13 +422,17 @@ public class StubDataSource {
private String[] getGenresString() {
ArrayList<String> results = new ArrayList<>();
for (AbstractGenre genre : mGenres) { results.add(genre.getTitle()); }
for (AbstractGenre genre : mGenres) {
results.add(genre.getTitle());
}
return results.toArray(new String[0]);
}
private String[] getFoldersString() {
ArrayList<String> results = new ArrayList<>();
for (AbstractFolder folder : mFolders) { results.add(folder.getTitle()); }
for (AbstractFolder folder : mFolders) {
results.add(folder.getTitle());
}
return results.toArray(new String[0]);
}

View File

@ -6,6 +6,7 @@ import android.os.Parcel;
import android.util.SparseArray;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.interfaces.IMedia;
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper;
public class StubMediaWrapper extends AbstractMediaWrapper {
@ -28,7 +29,7 @@ public class StubMediaWrapper extends AbstractMediaWrapper {
}
public StubMediaWrapper(Uri uri) { super(uri); }
public StubMediaWrapper(Media media) { super(media); }
public StubMediaWrapper(IMedia media) { super(media); }
public StubMediaWrapper(Parcel in) { super(in); }
private SparseArray<Long> mMetaLong = new SparseArray<>();

View File

@ -3,10 +3,13 @@ package org.videolan.medialibrary.stubs;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import android.webkit.URLUtil;
import androidx.annotation.NonNull;
import org.videolan.libvlc.util.Extensions;
import org.videolan.medialibrary.MLServiceLocator;
import org.videolan.medialibrary.Tools;
import org.videolan.medialibrary.interfaces.AbstractMedialibrary;
import org.videolan.medialibrary.interfaces.media.AbstractAlbum;
import org.videolan.medialibrary.interfaces.media.AbstractArtist;
@ -19,6 +22,7 @@ import org.videolan.medialibrary.media.SearchAggregate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
public class StubMedialibrary extends AbstractMedialibrary {
@ -32,6 +36,11 @@ public class StubMedialibrary extends AbstractMedialibrary {
return ML_INIT_SUCCESS;
}
@Override
public boolean isStarted() {
return true;
}
public void start() {
isMedialibraryStarted = true;
synchronized (onMedialibraryReadyListeners) {
@ -306,8 +315,8 @@ public class StubMedialibrary extends AbstractMedialibrary {
public AbstractMediaWrapper[] lastMediaPlayed() {
ArrayList<AbstractMediaWrapper> results = new ArrayList<>();
for (AbstractMediaWrapper media : dt.mHistory) {
if (media.getItemType() == AbstractMediaWrapper.TYPE_VIDEO ||
media.getItemType() == AbstractMediaWrapper.TYPE_AUDIO) results.add(media);
if (media.getType() == AbstractMediaWrapper.TYPE_VIDEO ||
media.getType() == AbstractMediaWrapper.TYPE_AUDIO) results.add(media);
// the native method specifies an nbItems of 100, offset 0
if (results.size() >= 100) break;
}
@ -317,7 +326,7 @@ public class StubMedialibrary extends AbstractMedialibrary {
public AbstractMediaWrapper[] lastStreamsPlayed() {
ArrayList<AbstractMediaWrapper> results = new ArrayList<>();
for (AbstractMediaWrapper media : dt.mHistory) {
if (media.getItemType() == AbstractMediaWrapper.TYPE_STREAM) results.add(media);
if (media.getType() == AbstractMediaWrapper.TYPE_STREAM) results.add(media);
// the native method specifies an nbItems of 100, offset 0
if (results.size() >= 100) break;
}
@ -347,15 +356,19 @@ public class StubMedialibrary extends AbstractMedialibrary {
// TODO Handle uri to mrl
private AbstractMediaWrapper getMedia(String mrl, String title) {
mrl = Tools.encodeVLCMrl(mrl);
for (AbstractMediaWrapper media : dt.mVideoMediaWrappers) {
if (media.getTitle().equals(title)) return media;
if (media.getLocation().equals(mrl)) return media;
}
for (AbstractMediaWrapper media : dt.mAudioMediaWrappers) {
if (media.getTitle().equals(title)) return media;
if (media.getLocation().equals(mrl)) return media;
}
for (AbstractMediaWrapper media : dt.mStreamMediaWrappers) {
if (media.getTitle().equals(title)) return media;
if (media.getLocation().equals(mrl)) return media;
}
if (!URLUtil.isNetworkUrl(mrl))
return dt.addMediaWrapper(mrl, title, AbstractMediaWrapper.TYPE_ALL);
return null;
}
@ -400,7 +413,7 @@ public class StubMedialibrary extends AbstractMedialibrary {
}
public AbstractMediaWrapper addStream(String mrl, String title) {
return null;
return dt.addMediaWrapper(mrl, title, AbstractMediaWrapper.TYPE_STREAM);
}
public AbstractFolder[] getFolders(int type, int sort, boolean desc, int nbItems, int offset) {

View File

@ -22,7 +22,7 @@ package org.videolan.tools
open class SingletonHolder<T, in A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
@Volatile protected var instance: T? = null
@Volatile var instance: T? = null
fun getInstance(arg: A) = instance ?: synchronized(this) {
val i2 = instance

View File

@ -273,6 +273,7 @@ dependencies {
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.ext.kotlinx_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.ext.kotlinx_version"
@ -282,8 +283,7 @@ dependencies {
implementation "com.squareup.moshi:moshi-adapters:$rootProject.ext.moshi"
implementation("com.squareup.okhttp3:logging-interceptor:4.2.1")
// Test
testImplementation project(":medialibrary")
// Tests
androidTestImplementation "androidx.test.espresso:espresso-contrib:$rootProject.espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$rootProject.espressoVersion"
testImplementation "junit:junit:$rootProject.ext.junitVersion"
@ -295,15 +295,11 @@ dependencies {
testImplementation "androidx.test:core:$rootProject.ext.supportTest"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$rootProject.ext.kotlinx_version"
testImplementation "org.mockito:mockito-core:$rootProject.ext.mockito"
testImplementation "io.mockk:mockk:$rootProject.ext.mockk"
testImplementation "org.powermock:powermock-api-mockito2:$rootProject.ext.powerMock"
testImplementation "org.powermock:powermock-module-junit4:$rootProject.ext.powerMock"
// testImplementation "org.powermock:powermock-module-junit4-rule:$rootProject.ext.powerMock"
testImplementation "org.powermock:powermock-module-junit4-rule-agent:$rootProject.ext.powerMock"
testImplementation "org.powermock:powermock-classloading-xstream:$rootProject.ext.powerMock"
testImplementation "com.jraska.livedata:testing-ktx:$rootProject.ext.livedataTest"
testImplementation "org.robolectric:robolectric:$rootProject.ext.robolectric"
// unmock 'org.robolectric:android-all:7.1.0_r7-robolectric-0'
androidTestImplementation 'androidx.test:rules:1.3.0-alpha01'
}

View File

@ -49,10 +49,10 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.actor
import org.videolan.libvlc.IVLCVout
import org.videolan.libvlc.Media
import org.videolan.libvlc.MediaPlayer
import org.videolan.libvlc.RendererItem
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.libvlc.interfaces.IVLCVout
import org.videolan.libvlc.util.AndroidUtil
import org.videolan.medialibrary.interfaces.AbstractMedialibrary
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
@ -184,7 +184,7 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
}
MediaPlayer.Event.EncounteredError -> executeUpdate()
MediaPlayer.Event.PositionChanged -> if (widget != 0) updateWidgetPosition(event.positionChanged)
MediaPlayer.Event.ESAdded -> if (event.esChangedType == Media.Track.Type.Video && (playlistManager.videoBackground || !playlistManager.switchToVideo())) {
MediaPlayer.Event.ESAdded -> if (event.esChangedType == IMedia.Track.Type.Video && (playlistManager.videoBackground || !playlistManager.switchToVideo())) {
/* CbAction notification content intent: resume video or resume audio activity */
updateMetadata()
}
@ -333,7 +333,7 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
@MainThread
get() = playlistManager.player.getLength()
val lastStats: Media.Stats?
val lastStats: IMedia.Stats?
get() = playlistManager.player.previousMediaStats
val isPlayingPopup: Boolean
@ -409,7 +409,7 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
@MainThread
get() = playlistManager.player.getVideoTracks()
val currentVideoTrack: Media.VideoTrack?
val currentVideoTrack: IMedia.VideoTrack?
@MainThread
get() = playlistManager.player.getCurrentVideoTrack()
@ -439,7 +439,7 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
interface Callback {
fun update()
fun onMediaEvent(event: Media.Event)
fun onMediaEvent(event: IMedia.Event)
fun onMediaPlayerEvent(event: MediaPlayer.Event)
}
@ -579,7 +579,7 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
override fun onBind(intent: Intent): IBinder? {
dispatcher.onServicePreSuperOnBind()
return if (MediaBrowserServiceCompat.SERVICE_INTERFACE == intent.action) super.onBind(intent) else binder
return if (SERVICE_INTERFACE == intent.action) super.onBind(intent) else binder
}
val vout: IVLCVout?
@ -645,7 +645,7 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
private fun canSwitchToVideo() = playlistManager.player.canSwitchToVideo()
fun onMediaEvent(event: Media.Event) = cbActor.safeOffer(CbMediaEvent(event))
fun onMediaEvent(event: IMedia.Event) = cbActor.safeOffer(CbMediaEvent(event))
fun executeUpdate() {
cbActor.safeOffer(CbUpdate)
@ -1345,7 +1345,7 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
private sealed class CbAction
private object CbUpdate : CbAction()
private class CbMediaEvent(val event: Media.Event) : CbAction()
private class CbMediaEvent(val event: IMedia.Event) : CbAction()
private class CbMediaPlayerEvent(val event: MediaPlayer.Event) : CbAction()
private class CbAdd(val cb: PlaybackService.Callback) : CbAction()
private class CbRemove(val cb: PlaybackService.Callback) : CbAction()

View File

@ -11,8 +11,9 @@ import android.os.Build
import android.util.Log
import android.view.Surface
import kotlinx.coroutines.*
import org.videolan.libvlc.Media
import org.videolan.libvlc.FactoryManager
import org.videolan.libvlc.MediaPlayer
import org.videolan.libvlc.interfaces.IMediaFactory
import org.videolan.vlc.media.MediaPlayerEventListener
import org.videolan.vlc.media.PlayerController
import org.videolan.vlc.util.VLCInstance
@ -28,6 +29,8 @@ private const val TAG = "PreviewInputService"
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class PreviewVideoInputService : TvInputService(), CoroutineScope by MainScope() {
internal val mFactory = FactoryManager.getFactory(IMediaFactory.factoryId) as IMediaFactory
override fun onCreateSession(inputId: String): TvInputService.Session? {
return PreviewSession(this)
}
@ -60,7 +63,7 @@ class PreviewVideoInputService : TvInputService(), CoroutineScope by MainScope()
return@launch
}
try {
val media = Media(VLCInstance.get(this@PreviewVideoInputService), mw.uri)
val media = mFactory.getFromUri(VLCInstance.get(this@PreviewVideoInputService), mw.uri)
val start = if (mw.length <= 0L) 0L else mw.length.random()
media.addOption(":start-time=${start/1000L}")
awaitSurface()

View File

@ -38,6 +38,11 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.launch
import org.videolan.libvlc.Dialog
import org.videolan.libvlc.FactoryManager
import org.videolan.libvlc.LibVLCFactory
import org.videolan.libvlc.MediaFactory
import org.videolan.libvlc.interfaces.ILibVLCFactory
import org.videolan.libvlc.interfaces.IMediaFactory
import org.videolan.libvlc.util.AndroidUtil
import org.videolan.tools.isStarted
import org.videolan.vlc.gui.SendCrashActivity
@ -54,6 +59,9 @@ class VLCApplication : MultiDexApplication(), Dialog.Callbacks by DialogDelegate
init {
instance = this
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
FactoryManager.registerFactory(IMediaFactory.factoryId, MediaFactory())
FactoryManager.registerFactory(ILibVLCFactory.factoryId, LibVLCFactory())
}
@TargetApi(Build.VERSION_CODES.O)

View File

@ -22,7 +22,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.*
import org.videolan.libvlc.Media
import org.videolan.libvlc.FactoryManager
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.libvlc.interfaces.IMediaFactory
import org.videolan.libvlc.util.Extensions
import org.videolan.medialibrary.Tools
import org.videolan.medialibrary.interfaces.AbstractMedialibrary
@ -229,9 +231,10 @@ class InfoActivity : AudioPlayerContainerActivity(), View.OnClickListener, PathA
class InfoModel : ViewModel() {
internal val hasSubs = MutableLiveData<Boolean>()
internal val mediaTracks = MutableLiveData<List<Media.Track>>()
internal val mediaTracks = MutableLiveData<List<IMedia.Track>>()
internal val sizeText = MutableLiveData<String>()
internal val cover = MutableLiveData<Bitmap>()
internal val mMediaFactory = FactoryManager.getFactory(IMediaFactory.factoryId) as IMediaFactory
internal fun getCover(mrl: String?, width: Int) = viewModelScope.launch {
cover.value = mrl?.let { withContext(Dispatchers.IO) { AudioUtil.readCoverBitmap(Uri.decode(it), width) } }
@ -240,16 +243,16 @@ class InfoModel : ViewModel() {
internal fun parseTracks(context: Context, mw: AbstractMediaWrapper) = viewModelScope.launch {
val media = withContext(Dispatchers.IO) {
val libVlc = VLCInstance[context]
Media(libVlc, mw.uri).apply { parse() }
mMediaFactory.getFromUri(libVlc, mw.uri).apply { parse() }
}
if (!isActive) return@launch
var subs = false
val trackCount = media.trackCount
val tracks = LinkedList<Media.Track>()
val tracks = LinkedList<IMedia.Track>()
for (i in 0 until trackCount) {
val track = media.getTrack(i)
tracks.add(track)
subs = subs or (track.type == Media.Track.Type.Text)
subs = subs or (track.type == IMedia.Track.Type.Text)
}
media.release()
hasSubs.value = subs

View File

@ -28,12 +28,13 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import org.videolan.libvlc.Media
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.vlc.R
import org.videolan.vlc.util.readableSize
class MediaInfoAdapter : RecyclerView.Adapter<MediaInfoAdapter.ViewHolder>() {
private lateinit var inflater: LayoutInflater
private var dataset: List<Media.Track>? = null
private var dataset: List<IMedia.Track>? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
if (!::inflater.isInitialized)
@ -47,17 +48,17 @@ class MediaInfoAdapter : RecyclerView.Adapter<MediaInfoAdapter.ViewHolder>() {
val textBuilder = StringBuilder()
val res = holder.itemView.context.resources
when (track.type) {
Media.Track.Type.Audio -> {
IMedia.Track.Type.Audio -> {
title = res.getString(R.string.track_audio)
appendCommon(textBuilder, res, track)
appendAudio(textBuilder, res, track as Media.AudioTrack)
appendAudio(textBuilder, res, track as IMedia.AudioTrack)
}
Media.Track.Type.Video -> {
IMedia.Track.Type.Video -> {
title = res.getString(R.string.track_video)
appendCommon(textBuilder, res, track)
appendVideo(textBuilder, res, track as Media.VideoTrack)
appendVideo(textBuilder, res, track as IMedia.VideoTrack)
}
Media.Track.Type.Text -> {
IMedia.Track.Type.Text -> {
title = res.getString(R.string.track_text)
appendCommon(textBuilder, res, track)
}
@ -69,14 +70,14 @@ class MediaInfoAdapter : RecyclerView.Adapter<MediaInfoAdapter.ViewHolder>() {
override fun getItemCount() = dataset?.size ?: 0
fun setTracks(tracks: List<Media.Track>) {
fun setTracks(tracks: List<IMedia.Track>) {
val size = itemCount
dataset = tracks
if (size > 0) notifyItemRangeRemoved(0, size - 1)
notifyItemRangeInserted(0, tracks.size)
}
private fun appendCommon(textBuilder: StringBuilder, res: Resources, track: Media.Track) {
private fun appendCommon(textBuilder: StringBuilder, res: Resources, track: IMedia.Track) {
if (track.bitrate != 0)
textBuilder.append(res.getString(R.string.track_bitrate_info, track.bitrate.toLong().readableSize()))
textBuilder.append(res.getString(R.string.track_codec_info, track.codec))
@ -84,12 +85,12 @@ class MediaInfoAdapter : RecyclerView.Adapter<MediaInfoAdapter.ViewHolder>() {
textBuilder.append(res.getString(R.string.track_language_info, track.language))
}
private fun appendAudio(textBuilder: StringBuilder, res: Resources, track: Media.AudioTrack) {
private fun appendAudio(textBuilder: StringBuilder, res: Resources, track: IMedia.AudioTrack) {
textBuilder.append(res.getQuantityString(R.plurals.track_channels_info_quantity, track.channels, track.channels))
textBuilder.append(res.getString(R.string.track_samplerate_info, track.rate))
}
private fun appendVideo(textBuilder: StringBuilder, res: Resources, track: Media.VideoTrack) {
private fun appendVideo(textBuilder: StringBuilder, res: Resources, track: IMedia.VideoTrack) {
val framerate = track.frameRateNum / track.frameRateDen.toDouble()
if (track.width != 0 && track.height != 0)
textBuilder.append(res.getString(R.string.track_resolution_info, track.width, track.height))

View File

@ -36,9 +36,10 @@ import androidx.core.app.NotificationCompat
import androidx.core.view.GestureDetectorCompat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.videolan.libvlc.IVLCVout
import org.videolan.libvlc.Media
import org.videolan.libvlc.MediaPlayer
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.libvlc.interfaces.IVLCVout
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.vlc.PlaybackService
import org.videolan.vlc.R
@ -184,7 +185,7 @@ class PopupManager constructor(private val mService: PlaybackService) : Playback
override fun update() {}
override fun onMediaEvent(event: Media.Event) {}
override fun onMediaEvent(event: IMedia.Event) {}
override fun onMediaPlayerEvent(event: MediaPlayer.Event) {
when (event.type) {

View File

@ -73,6 +73,7 @@ import kotlinx.coroutines.withContext
import org.videolan.libvlc.Media
import org.videolan.libvlc.MediaPlayer
import org.videolan.libvlc.RendererItem
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.libvlc.util.AndroidUtil
import org.videolan.libvlc.util.DisplayManager
import org.videolan.libvlc.util.VLCVideoLayout
@ -892,7 +893,7 @@ open class VideoPlayerActivity : AppCompatActivity(), IPlaybackSettingsControlle
if (data.hasExtra(EXTRA_MRL)) {
service?.addSubtitleTrack(Uri.parse(data.getStringExtra(EXTRA_MRL)), false)
service?.currentMediaWrapper?.let {
SlaveRepository.getInstance(this).saveSlave(it.location, Media.Slave.Type.Subtitle, 2, data.getStringExtra(EXTRA_MRL))
SlaveRepository.getInstance(this).saveSlave(it.location, IMedia.Slave.Type.Subtitle, 2, data.getStringExtra(EXTRA_MRL))
}
addNextTrack = true
} else if (BuildConfig.DEBUG) Log.d(TAG, "Subtitle selection dialog was cancelled")
@ -1459,10 +1460,10 @@ open class VideoPlayerActivity : AppCompatActivity(), IPlaybackSettingsControlle
playlistModel?.update()
}
override fun onMediaEvent(event: Media.Event) {
override fun onMediaEvent(event: IMedia.Event) {
when (event.type) {
Media.Event.ParsedChanged -> updateNavStatus()
Media.Event.MetaChanged -> {
IMedia.Event.ParsedChanged -> updateNavStatus()
IMedia.Event.MetaChanged -> {
}
}
}
@ -1483,14 +1484,14 @@ open class VideoPlayerActivity : AppCompatActivity(), IPlaybackSettingsControlle
MediaPlayer.Event.ESAdded -> {
if (menuIdx == -1) {
val media = medialibrary.findMedia(service.currentMediaWrapper) ?: return
if (event.esChangedType == Media.Track.Type.Audio) {
if (event.esChangedType == IMedia.Track.Type.Audio) {
setESTrackLists()
runIO(Runnable {
val audioTrack = media.getMetaLong(AbstractMediaWrapper.META_AUDIOTRACK).toInt()
if (audioTrack != 0 || currentAudioTrack != -2)
service.setAudioTrack(if (media.id == 0L) currentAudioTrack else audioTrack)
})
} else if (event.esChangedType == Media.Track.Type.Text) {
} else if (event.esChangedType == IMedia.Track.Type.Text) {
setESTrackLists()
runIO(Runnable {
val spuTrack = media.getMetaLong(AbstractMediaWrapper.META_SUBTITLE_TRACK).toInt()
@ -1504,23 +1505,23 @@ open class VideoPlayerActivity : AppCompatActivity(), IPlaybackSettingsControlle
})
}
}
if (menuIdx == -1 && event.esChangedType == Media.Track.Type.Video) {
if (menuIdx == -1 && event.esChangedType == IMedia.Track.Type.Video) {
handler.removeMessages(CHECK_VIDEO_TRACKS)
handler.sendEmptyMessageDelayed(CHECK_VIDEO_TRACKS, 1000)
}
invalidateESTracks(event.esChangedType)
}
MediaPlayer.Event.ESDeleted -> {
if (menuIdx == -1 && event.esChangedType == Media.Track.Type.Video) {
if (menuIdx == -1 && event.esChangedType == IMedia.Track.Type.Video) {
handler.removeMessages(CHECK_VIDEO_TRACKS)
handler.sendEmptyMessageDelayed(CHECK_VIDEO_TRACKS, 1000)
}
invalidateESTracks(event.esChangedType)
}
MediaPlayer.Event.ESSelected -> if (event.esChangedType == Media.Track.Type.Video) {
MediaPlayer.Event.ESSelected -> if (event.esChangedType == IMedia.Track.Type.Video) {
val vt = service.currentVideoTrack
if (vt != null)
fov = if (vt.projection == Media.VideoTrack.Projection.Rectangular) 0f else DEFAULT_FOV
fov = if (vt.projection == IMedia.VideoTrack.Projection.Rectangular) 0f else DEFAULT_FOV
}
MediaPlayer.Event.SeekableChanged -> updateSeekable(event.seekable)
MediaPlayer.Event.PausableChanged -> updatePausable(event.pausable)
@ -2223,8 +2224,8 @@ open class VideoPlayerActivity : AppCompatActivity(), IPlaybackSettingsControlle
private fun invalidateESTracks(type: Int) {
when (type) {
Media.Track.Type.Audio -> audioTracksList = null
Media.Track.Type.Text -> subtitleTracksList = null
IMedia.Track.Type.Audio -> audioTracksList = null
IMedia.Track.Type.Text -> subtitleTracksList = null
}
}

View File

@ -121,10 +121,11 @@ class BenchActivity : ShallowVideoPlayer() {
val sharedPref = Settings.getInstance(this)
mOldOpenglValue = sharedPref.getString(PREFERENCE_OPENGL, "-1")
mOldHistoryBoolean = sharedPref.getBoolean(PREFERENCE_PLAYBACK_HISTORY, true)
val editor = sharedPref.edit()
editor.putString(PREFERENCE_OPENGL, "0")
editor.putBoolean(PREFERENCE_PLAYBACK_HISTORY, false)
editor.commit()
sharedPref.edit().run {
putString(PREFERENCE_OPENGL, "0")
putBoolean(PREFERENCE_PLAYBACK_HISTORY, false)
commit()
}
VLCInstance.restart()
this.service?.restartMediaPlayer()
}
@ -480,10 +481,11 @@ class BenchActivity : ShallowVideoPlayer() {
/* Resetting vout preference to it value before the benchmark */
if (mIsHardware && mOldOpenglValue != "-2") {
val sharedPref = Settings.getInstance(this)
val editor = sharedPref.edit()
editor.putString(PREFERENCE_OPENGL, mOldOpenglValue)
editor.putBoolean(PREFERENCE_PLAYBACK_HISTORY, mOldHistoryBoolean)
editor.commit()
sharedPref.edit().run {
putString(PREFERENCE_OPENGL, mOldOpenglValue)
putBoolean(PREFERENCE_PLAYBACK_HISTORY, mOldHistoryBoolean)
commit()
}
VLCInstance.restart()
}
/* Case of error in VideoPlayerActivity, then finish is not overridden */

View File

@ -33,7 +33,7 @@ import android.util.AttributeSet
import android.view.*
import android.widget.RelativeLayout
import androidx.core.view.GestureDetectorCompat
import org.videolan.libvlc.IVLCVout
import org.videolan.libvlc.interfaces.IVLCVout
import org.videolan.libvlc.util.AndroidUtil
import org.videolan.vlc.R

View File

@ -10,6 +10,9 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.actor
import org.videolan.libvlc.*
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.libvlc.interfaces.IMediaList
import org.videolan.libvlc.interfaces.IVLCVout
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.vlc.BuildConfig
import org.videolan.vlc.PlaybackService
@ -34,7 +37,7 @@ class PlayerController(val context: Context) : IVLCVout.Callback, MediaPlayer.Ev
var switchToVideo = false
var seekable = false
var pausable = false
var previousMediaStats: Media.Stats? = null
var previousMediaStats: IMedia.Stats? = null
private set
@Volatile var hasRenderer = false
private set
@ -43,7 +46,7 @@ class PlayerController(val context: Context) : IVLCVout.Callback, MediaPlayer.Ev
fun canDoPassthrough() = mediaplayer.hasMedia() && !mediaplayer.isReleased && mediaplayer.canDoPassthrough()
fun getMedia(): Media? = mediaplayer.media
fun getMedia(): IMedia? = mediaplayer.media
fun play() {
if (mediaplayer.hasMedia() && !mediaplayer.isReleased) mediaplayer.play()
@ -68,7 +71,7 @@ class PlayerController(val context: Context) : IVLCVout.Callback, MediaPlayer.Ev
}
private var mediaplayerEventListener: MediaPlayerEventListener? = null
internal suspend fun startPlayback(media: Media, listener: MediaPlayerEventListener, time: Long) {
internal suspend fun startPlayback(media: IMedia, listener: MediaPlayerEventListener, time: Long) {
mediaplayerEventListener = listener
resetPlaybackState(time, media.duration)
mediaplayer.setEventListener(null)
@ -120,7 +123,7 @@ class PlayerController(val context: Context) : IVLCVout.Callback, MediaPlayer.Ev
fun getVideoTrack() = if (!mediaplayer.isReleased && mediaplayer.hasMedia()) mediaplayer.videoTrack else -1
fun getCurrentVideoTrack(): Media.VideoTrack? = if (!mediaplayer.isReleased && mediaplayer.hasMedia()) mediaplayer.currentVideoTrack else null
fun getCurrentVideoTrack(): IMedia.VideoTrack? = if (!mediaplayer.isReleased && mediaplayer.hasMedia()) mediaplayer.currentVideoTrack else null
fun getAudioTracksCount() = if (!mediaplayer.isReleased && mediaplayer.hasMedia()) mediaplayer.audioTracksCount else 0
@ -144,9 +147,9 @@ class PlayerController(val context: Context) : IVLCVout.Callback, MediaPlayer.Ev
fun setVideoTrackEnabled(enabled: Boolean) = mediaplayer.setVideoTrackEnabled(enabled)
fun addSubtitleTrack(path: String, select: Boolean) = mediaplayer.addSlave(Media.Slave.Type.Subtitle, path, select)
fun addSubtitleTrack(path: String, select: Boolean) = mediaplayer.addSlave(IMedia.Slave.Type.Subtitle, path, select)
fun addSubtitleTrack(uri: Uri, select: Boolean) = mediaplayer.addSlave(Media.Slave.Type.Subtitle, uri, select)
fun addSubtitleTrack(uri: Uri, select: Boolean) = mediaplayer.addSlave(IMedia.Slave.Type.Subtitle, uri, select)
fun getSpuTracks(): Array<out MediaPlayer.TrackDescription>? = mediaplayer.spuTracks
@ -190,7 +193,7 @@ class PlayerController(val context: Context) : IVLCVout.Callback, MediaPlayer.Ev
setPlaybackStopped()
}
fun setSlaves(media: Media, mw: AbstractMediaWrapper) = launch {
fun setSlaves(media: IMedia, mw: AbstractMediaWrapper) = launch {
if (mediaplayer.isReleased) return@launch
val slaves = mw.slaves
slaves?.let { it.forEach { slave -> media.addSlave(slave) } }
@ -234,9 +237,9 @@ class PlayerController(val context: Context) : IVLCVout.Callback, MediaPlayer.Ev
* @return true if UI needs to be updated
*/
internal fun updateCurrentMeta(id: Int, mw: AbstractMediaWrapper?): Boolean {
if (id == Media.Meta.Publisher) return false
if (id == IMedia.Meta.Publisher) return false
mw?.updateMeta(mediaplayer)
return id != Media.Meta.NowPlaying || mw?.nowPlaying !== null
return id != IMedia.Meta.NowPlaying || mw?.nowPlaying !== null
}
fun setPreviousStats() {
@ -269,7 +272,7 @@ class PlayerController(val context: Context) : IVLCVout.Callback, MediaPlayer.Ev
fun setVolume(volume: Int) = if (!mediaplayer.isReleased) mediaplayer.setVolume(volume) else -1
suspend fun expand(): MediaList? {
suspend fun expand(): IMediaList? {
return mediaplayer.media?.let {
return withContext(playerContext) {
mediaplayer.setEventListener(null)
@ -336,7 +339,7 @@ internal interface MediaPlayerEventListener {
suspend fun onEvent(event: MediaPlayer.Event)
}
private fun Array<Media.Slave>?.contains(item: Media.Slave) : Boolean {
private fun Array<IMedia.Slave>?.contains(item: IMedia.Slave) : Boolean {
if (this == null) return false
for (slave in this) if (slave.uri == item.uri) return true
return false

View File

@ -12,9 +12,9 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.actor
import org.videolan.libvlc.Media
import org.videolan.libvlc.MediaPlayer
import org.videolan.libvlc.RendererItem
import org.videolan.libvlc.*
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.libvlc.interfaces.IMediaFactory
import org.videolan.libvlc.util.AndroidUtil
import org.videolan.medialibrary.MLServiceLocator
import org.videolan.medialibrary.interfaces.AbstractMedialibrary
@ -34,7 +34,7 @@ private const val PLAYLIST_REPEAT_MODE_KEY = "audio_repeat_mode" //we keep the o
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
class PlaylistManager(val service: PlaybackService) : MediaWrapperList.EventListener, Media.EventListener, CoroutineScope {
class PlaylistManager(val service: PlaybackService) : MediaWrapperList.EventListener, IMedia.EventListener, CoroutineScope {
override val coroutineContext = Dispatchers.Main.immediate + SupervisorJob()
companion object {
@ -67,6 +67,8 @@ class PlaylistManager(val service: PlaybackService) : MediaWrapperList.EventList
private var entryUrl : String? = null
val abRepeat by lazy(LazyThreadSafetyMode.NONE) { MutableLiveData<ABRepeat>().apply { value = ABRepeat() } }
internal val mMediaFactory = FactoryManager.getFactory(IMediaFactory.factoryId) as IMediaFactory
fun hasCurrentMedia() = isValidPosition(currentIndex)
fun hasPlaylist() = mediaList.size() > 1
@ -298,7 +300,7 @@ class PlaylistManager(val service: PlaybackService) : MediaWrapperList.EventList
return
}
val start = getStartTime(mw)
val media = Media(VLCInstance.get(service), uri)
val media = mMediaFactory.getFromUri(VLCInstance.get(service), uri)
media.addOption(":start-time=${start/1000L}")
VLCOptions.setMediaOptions(media, ctx, flags or mw.flags)
/* keeping only video during benchmark */
@ -709,15 +711,15 @@ class PlaylistManager(val service: PlaybackService) : MediaWrapperList.EventList
}
}
override fun onEvent(event: Media.Event) {
override fun onEvent(event: IMedia.Event) {
var update = true
when (event.type) {
Media.Event.MetaChanged -> {
IMedia.Event.MetaChanged -> {
/* Update Meta if file is already parsed */
if (parsed && player.updateCurrentMeta(event.metaId, getCurrentMedia())) service.executeUpdate()
if (BuildConfig.DEBUG) Log.i(TAG, "Media.Event.MetaChanged: " + event.metaId)
}
Media.Event.ParsedChanged -> {
IMedia.Event.ParsedChanged -> {
if (BuildConfig.DEBUG) Log.i(TAG, "Media.Event.ParsedChanged")
player.updateCurrentMeta(-1, getCurrentMedia())
parsed = true

View File

@ -33,6 +33,7 @@ import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import org.videolan.libvlc.Media
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.libvlc.util.MediaBrowser
import org.videolan.libvlc.util.MediaBrowser.EventListener
import org.videolan.medialibrary.MLServiceLocator
@ -41,6 +42,7 @@ import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.medialibrary.media.Storage
import org.videolan.vlc.R
import org.videolan.vlc.VLCApplication
import org.videolan.vlc.util.*
import java.util.*
@ -48,12 +50,15 @@ const val TAG = "VLC/BrowserProvider"
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<MediaLibraryItem>, val url: String?, private var showHiddenFiles: Boolean) : CoroutineScope, HeaderProvider() {
override val coroutineContext = Dispatchers.Main.immediate + SupervisorJob()
val loading = MutableLiveData<Boolean>().apply { value = false }
protected var mediabrowser: MediaBrowser? = null
var mediabrowser: MediaBrowser? = null
val coroutineContextProvider: CoroutineContextProvider
private var parsingJob : Job? = null
private var discoveryJob : Job? = null
@ -63,6 +68,12 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
val descriptionUpdate = MutableLiveData<Pair<Int, String>>()
internal val medialibrary = AbstractMedialibrary.getInstance()
init {
// BrowserProvider.registerCreator { CoroutineContextProvider() }
// coroutineContextProvider = BrowserProvider.get(this)
coroutineContextProvider = CoroutineContextProvider()
}
private val completionHandler : CompletionHandler = object : CompletionHandler {
override fun invoke(cause: Throwable?) {
if (mediabrowser != null) AppScope.launch(Dispatchers.IO) { // use global scope because current is cancelled
@ -89,6 +100,8 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
}
protected open fun initBrowser() {
// BrowserProvider.registerCreator { MediaBrowser(VLCInstance[context], it, browserHandler) }
// if (mediabrowser == null) mediabrowser = BrowserProvider.get(this)
if (mediabrowser == null) mediabrowser = MediaBrowser(VLCInstance[context], null, browserHandler)
}
@ -130,7 +143,7 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
private suspend fun filesFlow(url: String? = this.url, interact : Boolean = true) = channelFlow {
val listener = object : EventListener {
override fun onMediaAdded(index: Int, media: Media) {
override fun onMediaAdded(index: Int, media: IMedia) {
if (!isClosedForSend) offer(media.apply { retain() })
}
@ -138,7 +151,7 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
if (!isClosedForSend) close()
}
override fun onMediaRemoved(index: Int, media: Media) {}
override fun onMediaRemoved(index: Int, media: IMedia) {}
}
requestBrowsing(url, listener, interact)
awaitClose { if (url != null) browserActor.post(ClearListener) }
@ -174,12 +187,12 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
private suspend fun parseSubDirectoriesImpl(list : List<MediaLibraryItem>? = null) {
if (list === null && dataset.value.isEmpty()) return
val currentMediaList = list ?: withContext(Dispatchers.Main) { dataset.value.toList() }
val currentMediaList = list ?: withContext(coroutineContextProvider.Main) { dataset.value.toList() }
val directories: MutableList<AbstractMediaWrapper> = ArrayList()
val files: MutableList<AbstractMediaWrapper> = ArrayList()
foldersContentMap.clear()
coroutineScope { // allow child coroutine to be cancelled without closing the actor.
parsingJob = launch (Dispatchers.IO) {
parsingJob = launch (coroutineContextProvider.IO) {
initBrowser()
var currentParsedPosition = -1
loop@ while (++currentParsedPosition < currentMediaList.size) {
@ -210,12 +223,12 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
// all subitems are in
getDescription(directories.size, files.size).takeIf { it.isNotEmpty() }?.let {
val position = currentParsedPosition
withContext(Dispatchers.Main) {
withContext(coroutineContextProvider.Main) {
item.description = it
descriptionUpdate.value = Pair(position, it)
}
directories.addAll(files)
withContext(Dispatchers.Main) { foldersContentMap.put(item, directories.toMutableList()) }
withContext(coroutineContextProvider.Main) { foldersContentMap.put(item, directories.toMutableList()) }
}
directories.clear()
files.clear()
@ -238,8 +251,8 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
return sb.toString()
}
protected open suspend fun findMedia(media: Media): MediaLibraryItem? {
val mw = MLServiceLocator.getAbstractMediaWrapper(media)
protected open suspend fun findMedia(media: IMedia): MediaLibraryItem? {
val mw: AbstractMediaWrapper = MLServiceLocator.getAbstractMediaWrapper(media)
media.release()
if (!mw.isMedia()) {
if (mw.isBrowserMedia()) return mw
@ -247,7 +260,7 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
}
val uri = mw.uri
if ((mw.type == AbstractMediaWrapper.TYPE_AUDIO || mw.type == AbstractMediaWrapper.TYPE_VIDEO)
&& "file" == uri.scheme) return withContext(Dispatchers.IO) {
&& "file" == uri.scheme) return withContext(coroutineContextProvider.IO) {
medialibrary.getMedia(uri) ?: mw
}
return mw
@ -292,11 +305,11 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
protected fun removeList(url: String) = prefetchLists.remove(url)
fun saveList(media: AbstractMediaWrapper) = foldersContentMap[media]?.let { if (!it.isEmpty()) prefetchLists[media.location] = it }
fun saveList(media: AbstractMediaWrapper) = foldersContentMap[media]?.let { if (it.isNotEmpty()) prefetchLists[media.location] = it }
fun isFolderEmpty(mw: AbstractMediaWrapper) = foldersContentMap[mw]?.isEmpty() ?: true
companion object {
companion object : DependencyProvider<EventListener>() {
private val browserHandler by lazy {
val handlerThread = HandlerThread("vlc-provider", Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE)
handlerThread.start()

View File

@ -22,6 +22,7 @@ package org.videolan.vlc.providers
import android.content.Context
import org.videolan.libvlc.Media
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.libvlc.util.MediaBrowser
import org.videolan.medialibrary.MLServiceLocator
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
@ -38,7 +39,7 @@ class FilePickerProvider(context: Context, dataset: LiveDataset<MediaLibraryItem
mediabrowser?.setIgnoreFileTypes("db,nfo,ini,jpg,jpeg,ljpg,gif,png,pgm,pgmyuv,pbm,pam,tga,bmp,pnm,xpm,xcf,pcx,tif,tiff,lbm,sfv")
}
override suspend fun findMedia(media: Media) = MLServiceLocator.getAbstractMediaWrapper(media)?.takeIf { mw ->
override suspend fun findMedia(media: IMedia) = MLServiceLocator.getAbstractMediaWrapper(media)?.takeIf { mw ->
mw.type == AbstractMediaWrapper.TYPE_DIR || mw.type == AbstractMediaWrapper.TYPE_SUBTITLE
}

View File

@ -24,6 +24,8 @@ import android.content.Context
import android.net.Uri
import android.text.TextUtils
import org.videolan.libvlc.Media
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.medialibrary.media.Storage
import org.videolan.vlc.R
@ -59,9 +61,9 @@ class StorageProvider(context: Context, dataset: LiveDataset<MediaLibraryItem>,
dataset.value = storagesList
}
override suspend fun findMedia(media: Media) = media.takeIf { it.isStorage() }?.let { Storage(it.uri) }
override suspend fun findMedia(media: IMedia) = media.takeIf { it.isStorage() }?.let { Storage(it.uri) }
override fun computeHeaders(value: List<MediaLibraryItem>) {}
}
private fun Media.isStorage() = type == Media.Type.Directory
private fun IMedia.isStorage() = type == IMedia.Type.Directory

View File

@ -34,10 +34,11 @@ import org.videolan.vlc.database.MediaDatabase
import org.videolan.vlc.database.models.ExternalSub
import org.videolan.vlc.gui.dialogs.State
import org.videolan.vlc.gui.dialogs.SubtitleItem
import org.videolan.vlc.util.CoroutineContextProvider
import org.videolan.vlc.util.LiveDataMap
import java.io.File
class ExternalSubRepository(private val externalSubDao: ExternalSubDao ) {
class ExternalSubRepository(private val externalSubDao: ExternalSubDao, private val coroutineContextProvider: CoroutineContextProvider = CoroutineContextProvider()) {
private var _downloadingSubtitles = LiveDataMap<Long, SubtitleItem>()
@ -45,7 +46,7 @@ class ExternalSubRepository(private val externalSubDao: ExternalSubDao ) {
get() = _downloadingSubtitles as LiveData<Map<Long, SubtitleItem>>
fun saveDownloadedSubtitle(idSubtitle: String, subtitlePath: String, mediaPath: String, language: String, movieReleaseName: String): Job {
return GlobalScope.launch(Dispatchers.IO) { externalSubDao.insert(ExternalSub(idSubtitle, subtitlePath, mediaPath, language, movieReleaseName)) }
return GlobalScope.launch(coroutineContextProvider.IO) { externalSubDao.insert(ExternalSub(idSubtitle, subtitlePath, mediaPath, language, movieReleaseName)) }
}
fun getDownloadedSubtitles(mediaUri: Uri): LiveData<List<ExternalSub>> {

View File

@ -82,5 +82,9 @@ class OpenSubtitleRepository(private val openSubtitleService: IOpenSubtitleServi
}
}
companion object { fun getInstance() = OpenSubtitleRepository(OpenSubtitleClient.instance)}
companion object {
// To ensure the instance can be overridden in tests.
var instance = lazy { OpenSubtitleRepository(OpenSubtitleClient.instance) }
fun getInstance() = instance.value
}
}

View File

@ -28,6 +28,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.videolan.libvlc.Media
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.tools.IOScopedObject
import org.videolan.tools.SingletonHolder
@ -50,7 +51,7 @@ class SlaveRepository(private val slaveDao:SlaveDao) : IOScopedObject() {
}
}
suspend fun getSlaves(mrl: String): List<Media.Slave> {
suspend fun getSlaves(mrl: String): List<IMedia.Slave> {
return withContext(Dispatchers.IO) {
val slaves = try {
slaveDao.get(mrl)
@ -61,7 +62,7 @@ class SlaveRepository(private val slaveDao:SlaveDao) : IOScopedObject() {
var uri = it.uri
if (uri.isNotEmpty())
uri = Uri.decode(it.uri)
Media.Slave(it.type, it.priority, uri)
IMedia.Slave(it.type, it.priority, uri)
}
mediaSlaves
}

View File

@ -0,0 +1,10 @@
package org.videolan.vlc.util
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
open class CoroutineContextProvider {
open val Default by lazy { Dispatchers.Default }
open val IO by lazy { Dispatchers.IO }
open val Main: CoroutineDispatcher by lazy { Dispatchers.Main }
}

View File

@ -0,0 +1,31 @@
package org.videolan.vlc.util
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
@Suppress("UNCHECKED_CAST")
open class DependencyProvider<A> {
val objectMap: ConcurrentMap<String, Any> = ConcurrentHashMap()
val creatorMap: ConcurrentMap<String, (A) -> Any> = ConcurrentHashMap()
var overrideCreator = true
inline fun <T> getKey(clazz: Class<T>): String = clazz.name
inline fun <X : Any, reified T : X> registerCreator(clazz: Class<X>? = null, noinline creator: (A) -> T) {
val key = getKey(clazz ?: T::class.java)
if (overrideCreator || !creatorMap.containsKey(key))
creatorMap[key] = creator
if (objectMap.containsKey(key) && overrideCreator) {
objectMap.remove(key)
}
}
inline fun <X : Any, reified T : X> get(arg: A, clazz: Class<X>? = null): T {
val key = getKey(clazz ?: T::class.java)
if (!objectMap.containsKey(key))
objectMap[key] = creatorMap[key]?.invoke(arg)
return objectMap[key] as T
}
}

View File

@ -26,6 +26,7 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import org.videolan.libvlc.Media
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.libvlc.util.AndroidUtil
import org.videolan.medialibrary.interfaces.AbstractMedialibrary
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
@ -71,7 +72,7 @@ inline fun <reified T : ViewModel> Fragment.getModelWithActivity() = ViewModelPr
inline fun <reified T : ViewModel> Fragment.getModel() = ViewModelProviders.of(this).get(T::class.java)
inline fun <reified T : ViewModel> FragmentActivity.getModel() = ViewModelProviders.of(this).get(T::class.java)
fun Media?.canExpand() = this != null && (type == Media.Type.Directory || type == Media.Type.Playlist)
fun Media?.canExpand() = this != null && (type == IMedia.Type.Directory || type == IMedia.Type.Playlist)
suspend fun AppCompatActivity.share(media: AbstractMediaWrapper) {
val intentShareFile = Intent(Intent.ACTION_SEND)
val fileWithinMyDir = File(media.uri.path)

View File

@ -6,6 +6,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.videolan.libvlc.Media
import org.videolan.libvlc.MediaPlayer
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.medialibrary.interfaces.AbstractMedialibrary.*
import org.videolan.medialibrary.interfaces.media.AbstractAlbum
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
@ -294,7 +296,7 @@ object ModelsHelper {
object EmptyPBSCallback : PlaybackService.Callback {
override fun update() {}
override fun onMediaEvent(event: Media.Event) {}
override fun onMediaEvent(event: IMedia.Event) {}
override fun onMediaPlayerEvent(event: MediaPlayer.Event) {}
}

View File

@ -17,6 +17,7 @@ object Settings : SingletonHolder<SharedPreferences, Context>({ PreferenceManage
var showVideoThumbs = true
var tvUI = false
var listTitleEllipsize = 0
var overrideTvUI = false
fun init(prefs: SharedPreferences) {
showVideoThumbs = prefs.getBoolean(SHOW_VIDEO_THUMBNAILS, true)
@ -25,7 +26,7 @@ object Settings : SingletonHolder<SharedPreferences, Context>({ PreferenceManage
}
val showTvUi : Boolean
get() = AndroidDevices.isTv || tvUI
get() = !overrideTvUI && AndroidDevices.isTv || tvUI
}
@ -82,4 +83,4 @@ const val CRASH_DONT_ASK_AGAIN = "crash_dont_ask_again"
const val PLAYBACK_HISTORY = "playback_history"
const val RESUME_PLAYBACK = "resume_playback"
const val AUDIO_DUCKING = "audio_ducking"
const val AUDIO_DUCKING = "audio_ducking"

View File

@ -25,23 +25,28 @@ import android.app.Activity
import android.content.Context
import android.content.Intent
import android.util.Log
import org.videolan.libvlc.LibVLC
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.videolan.libvlc.FactoryManager
import org.videolan.libvlc.interfaces.ILibVLC
import org.videolan.libvlc.interfaces.ILibVLCFactory
import org.videolan.libvlc.util.VLCUtil
import org.videolan.vlc.VLCApplication
import org.videolan.vlc.VLCCrashHandler
import org.videolan.vlc.gui.CompatErrorActivity
@ObsoleteCoroutinesApi
object VLCInstance {
val TAG = "VLC/UiTools/VLCInstance"
@SuppressLint("StaticFieldLeak")
private var sLibVLC: LibVLC? = null
private var sLibVLC: ILibVLC? = null
internal val mLibVLCFactory = FactoryManager.getFactory(ILibVLCFactory.factoryId) as ILibVLCFactory
/** A set of utility functions for the VLC application */
@Synchronized
@Throws(IllegalStateException::class)
operator fun get(ctx: Context): LibVLC {
operator fun get(ctx: Context): ILibVLC {
if (sLibVLC == null) {
Thread.setDefaultUncaughtExceptionHandler(VLCCrashHandler())
@ -52,7 +57,7 @@ object VLCInstance {
}
// TODO change LibVLC signature to accept a List instead of an ArrayList
sLibVLC = LibVLC(context, VLCOptions.libOptions)
sLibVLC = mLibVLCFactory.getFromOptions(context, VLCOptions.libOptions)
}
return sLibVLC!!
}
@ -62,7 +67,7 @@ object VLCInstance {
fun restart() {
if (sLibVLC != null) {
sLibVLC!!.release()
sLibVLC = LibVLC(VLCApplication.appContext, VLCOptions.libOptions)
sLibVLC = mLibVLCFactory.getFromOptions(VLCApplication.appContext, VLCOptions.libOptions)
}
}

View File

@ -29,6 +29,7 @@ import android.util.Log
import androidx.annotation.MainThread
import org.videolan.libvlc.Media
import org.videolan.libvlc.MediaPlayer
import org.videolan.libvlc.interfaces.IMedia
import org.videolan.libvlc.util.AndroidUtil
import org.videolan.libvlc.util.HWDecoderUtil
import org.videolan.libvlc.util.VLCUtil
@ -200,7 +201,7 @@ object VLCOptions {
return ret
}
fun setMediaOptions(media: Media, context: Context, flags: Int) {
fun setMediaOptions(media: IMedia, context: Context, flags: Int) {
val noHardwareAcceleration = flags and AbstractMediaWrapper.MEDIA_NO_HWACCEL != 0
val noVideo = flags and AbstractMediaWrapper.MEDIA_VIDEO == 0
val benchmark = flags and AbstractMediaWrapper.MEDIA_BENCHMARK != 0

View File

@ -0,0 +1,19 @@
package org.videolan.vlc.util
import org.videolan.vlc.repository.BrowserFavRepository
import org.videolan.vlc.repository.DirectoryRepository
import org.videolan.vlc.repository.ExternalSubRepository
// Hacky way. Don't fix it.
fun ExternalSubRepository.Companion.applyMock(instance: ExternalSubRepository) {
this.instance = instance
}
fun DirectoryRepository.Companion.applyMock(instance: DirectoryRepository) {
this.instance = instance
}
fun BrowserFavRepository.Companion.applyMock(instance: BrowserFavRepository) {
this.instance = instance
}

View File

@ -0,0 +1,9 @@
package org.videolan.vlc.util
import kotlinx.coroutines.Dispatchers
class TestCoroutineContextProvider : CoroutineContextProvider() {
override val Default by lazy { Dispatchers.Unconfined }
override val IO by lazy { Dispatchers.Unconfined }
override val Main by lazy { Dispatchers.Unconfined }
}

View File

@ -22,11 +22,14 @@ package org.videolan.vlc.util
import android.net.Uri
import org.videolan.libvlc.Media
import org.videolan.vlc.api.OpenSubtitle
import org.videolan.vlc.api.QueryParameters
import org.videolan.vlc.database.models.BrowserFav
import org.videolan.vlc.database.models.CustomDirectory
import org.videolan.vlc.database.models.ExternalSub
import org.videolan.vlc.database.models.Slave
import java.io.File
import org.videolan.vlc.gui.dialogs.State
import org.videolan.vlc.gui.dialogs.SubtitleItem
object TestUtil {
private const val fakeUri: String = "https://www.videolan.org/fake_"
@ -39,15 +42,13 @@ object TestUtil {
fun createLocalUris(count: Int): List<String> {
return (0 until count).map {
"${fakeUri}_local$it"
"${fakeMediaUri}local_$it.mp4"
}
}
fun createLocalFavs(count: Int): List<BrowserFav> {
return (0 until count).map {
createLocalFav(Uri.parse(fakeUri + "local" + it),
"local" + 1,
null)
createLocalFav(Uri.parse("${fakeMediaUri}_$it.mp4"), "local$it", null)
}
}
@ -56,9 +57,7 @@ object TestUtil {
}
fun createNetworkUris(count: Int): List<String> {
return (0 until count).map {
"${fakeUri}_network$it"
}
return (0 until count).map { "${fakeUri}_network$it.mp4" }
}
fun createNetworkFavs(count: Int): List<BrowserFav> {
@ -76,13 +75,13 @@ object TestUtil {
subtitlePath: String,
mediaPath: String,
subLanguageID: String,
movieReleaseName: String ): ExternalSub {
movieReleaseName: String): ExternalSub {
return ExternalSub(idSubtitle, subtitlePath, mediaPath, subLanguageID, movieReleaseName)
}
fun createExternalSubsForMedia(mediaPath: String, mediaName: String, count: Int): List<ExternalSub> {
return (0 until count).map {
ExternalSub(it.toString(),"${fakeSubUri}$mediaName$it", mediaPath, "en", mediaName)
ExternalSub(it.toString(), "${fakeSubUri}$mediaName$it", mediaPath, "en", mediaName)
}
}
@ -90,13 +89,13 @@ object TestUtil {
return Slave(mediaPath, Media.Slave.Type.Subtitle, 2, uri)
}
fun createSubtitleSlavesForMedia(mediaName: String, count:Int): List<Slave> {
fun createSubtitleSlavesForMedia(mediaName: String, count: Int): List<Slave> {
return (0 until count).map {
createSubtitleSlave( "$fakeMediaUri$mediaName", "$fakeSubUri$mediaName$it.srt" )
createSubtitleSlave("$fakeMediaUri$mediaName", "$fakeSubUri$mediaName$it.srt")
}
}
fun createCustomDirectory(path: String): CustomDirectory{
fun createCustomDirectory(path: String): CustomDirectory {
return CustomDirectory(path)
}
@ -106,4 +105,32 @@ object TestUtil {
createCustomDirectory("$directory$it")
}
}
fun createDownloadingSubtitleItem(
idSubtitle: String,
mediaUri: Uri,
subLanguageID: String,
movieReleaseName: String,
zipDownloadLink: String): SubtitleItem = SubtitleItem(idSubtitle, mediaUri, subLanguageID, movieReleaseName, State.Downloading, zipDownloadLink)
fun createDownloadingSubtitleItem(
idSubtitle: String,
mediaPath: String,
subLanguageID: String,
movieReleaseName: String,
zipDownloadLink: String): SubtitleItem = TestUtil.createDownloadingSubtitleItem(idSubtitle, Uri.parse(mediaPath), subLanguageID, movieReleaseName, zipDownloadLink)
fun createOpenSubtitle(
idSubtitle: String,
subLanguageID: String,
movieReleaseName: String,
zipDownloadLink: String) = OpenSubtitle(
idSubtitle = idSubtitle, subLanguageID = subLanguageID, movieReleaseName = movieReleaseName, zipDownloadLink = zipDownloadLink,
idMovie = "", idMovieImdb = "", idSubMovieFile = "", idSubtitleFile = "", infoFormat = "", infoOther = "", infoReleaseGroup = "",
userID = "", iSO639 = "", movieFPS = "", languageName = "", subActualCD = "", subSumVotes = "", subAuthorComment = "", subComments = "",
score = 0.0, seriesEpisode = "", seriesIMDBParent = "", seriesSeason = "", subAddDate = "", subAutoTranslation = "", subBad = "", subDownloadLink = "",
subDownloadsCnt = "", subEncoding = "", subFeatured = "", subFileName = "", subForeignPartsOnly = "", subFormat = "", subFromTrusted = "", subHash = "",
subHD = "", subHearingImpaired = "", subLastTS = "", subRating = "", subSize = "", subSumCD = "", subtitlesLink = "", subTranslator = "", subTSGroup = "",
subTSGroupHash = "", movieByteSize = "", movieHash = "", movieTimeMS = "", queryParameters = QueryParameters("", "", ""), queryNumber = "",
userNickName = "", userRank = "", matchedBy = "", movieImdbRating = "", movieKind = "", movieName = "", movieNameEng = "", movieYear = "")
}

View File

@ -0,0 +1,60 @@
package org.videolan.vlc
import android.content.Context
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import io.mockk.MockKAnnotations
import io.mockk.clearAllMocks
import io.mockk.unmockkAll
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.test.setMain
import org.junit.*
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import org.videolan.medialibrary.MLServiceLocator
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
@Config(application = VLCTestApplication::class, manifest = Config.NONE)
open class BaseTest {
val context: Context = ApplicationProvider.getApplicationContext()
val application = (RuntimeEnvironment.application as VLCTestApplication)
//To prevent Method getMainLooper in android.os.Looper not mocked error when setting value for MutableLiveData
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
init {
MockKAnnotations.init(this)
}
@Before
open fun beforeTest() {
println("beforeTest")
}
@After
open fun afterTest() {
println("afterTest")
clearAllMocks()
}
companion object {
@BeforeClass
@JvmStatic
fun setupTestClass() {
Dispatchers.setMain(Dispatchers.Unconfined)
}
@AfterClass
@JvmStatic
fun cleanupTestClass() {
unmockkAll()
}
}
}

View File

@ -0,0 +1,21 @@
package org.videolan.vlc
import android.app.Application
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.videolan.libvlc.FactoryManager
import org.videolan.libvlc.ILibVLCFactory
import org.videolan.libvlc.IMediaFactory
import org.videolan.libvlc.test.TestLibVLCFactory
import org.videolan.libvlc.test.TestMediaFactory
import org.videolan.medialibrary.MLServiceLocator
@ExperimentalCoroutinesApi
@ObsoleteCoroutinesApi
class VLCTestApplication : Application() {
init {
MLServiceLocator.setLocatorMode(MLServiceLocator.LocatorMode.TESTS)
FactoryManager.registerFactory(IMediaFactory.factoryId, TestMediaFactory())
FactoryManager.registerFactory(ILibVLCFactory.factoryId, TestLibVLCFactory())
}
}

View File

@ -97,8 +97,8 @@ class ExternalSubRepositoryTest {
`when`(externalSubDao.get(foo)).thenReturn(fakeFooLiveDataSubtitles)
`when`(externalSubDao.get(bar)).thenReturn(fakeBarLiveDataSubtitles)
val fooSubtitles = getValue(externalSubRepository.getDownloadedSubtitles(foo))
val barSubtitles = getValue(externalSubRepository.getDownloadedSubtitles(bar))
val fooSubtitles = getValue(externalSubRepository.getDownloadedSubtitles(Uri.parse(foo)))
val barSubtitles = getValue(externalSubRepository.getDownloadedSubtitles(Uri.parse(bar)))
verify(externalSubDao, times(2)).get(ArgumentMatchers.anyString())
assertThat(fooSubtitles.size, `is`(0))
}
@ -139,8 +139,8 @@ class ExternalSubRepositoryTest {
`when`(externalSubDao.get(foo)).thenReturn(fakeFooLiveDataSubtitles)
`when`(externalSubDao.get(bar)).thenReturn(fakeBarLiveDataSubtitles)
val fooSubtitles = getValue(externalSubRepository.getDownloadedSubtitles(foo))
val barSubtitles = getValue(externalSubRepository.getDownloadedSubtitles(bar))
val fooSubtitles = getValue(externalSubRepository.getDownloadedSubtitles(Uri.parse(foo)))
val barSubtitles = getValue(externalSubRepository.getDownloadedSubtitles(Uri.parse(bar)))
verify(externalSubDao, times(2)).get(ArgumentMatchers.anyString())
assertThat(fooSubtitles.size, `is`(2))
assertThat(barSubtitles.size, `is`(2))

View File

@ -0,0 +1,78 @@
package org.videolan.vlc.viewmodels
import android.net.Uri
import com.jraska.livedata.test
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.junit.Test
import org.videolan.medialibrary.MLServiceLocator
import org.videolan.medialibrary.Medialibrary
import org.videolan.medialibrary.interfaces.AbstractMedialibrary
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.vlc.BaseTest
import org.videolan.vlc.util.TestCoroutineContextProvider
import org.videolan.vlc.util.TestUtil
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
class HistoryModelTest : BaseTest() {
private val mediaLibrary: AbstractMedialibrary = AbstractMedialibrary.getInstance()
private lateinit var historyModel: HistoryModel
override fun beforeTest() {
super.beforeTest()
mediaLibrary.clearHistory()
historyModel = HistoryModel(application, TestCoroutineContextProvider())
}
@Test
fun whenRefreshCalled_ListIsUpdated() {
val fakeMediaStrings = TestUtil.createLocalUris(2)
historyModel.refresh()
historyModel.dataset.test()
.awaitValue()
.assertValue(Medialibrary.EMPTY_COLLECTION.toMutableList())
val result = fakeMediaStrings.map {
val media = MLServiceLocator.getAbstractMediaWrapper(Uri.parse(it))
mediaLibrary.addToHistory(media.location, media.title)
media
}
historyModel.refresh()
val testResult = historyModel.dataset.test()
.awaitValue()
.value()
assertEquals(2, testResult.size)
assertEquals(result[0], testResult[0])
assertEquals(result[1], testResult[1])
}
@Test
fun whenListHasTwoItemsAndLastIsMovedUp_ListHasUpdatedItemsOrder() {
val fakeMediaStrings = TestUtil.createLocalUris(2)
val result = fakeMediaStrings.map {
val media = MLServiceLocator.getAbstractMediaWrapper(Uri.parse(it)).apply { type = AbstractMediaWrapper.TYPE_VIDEO }
mediaLibrary.addToHistory(media.location, media.title)
media
}
historyModel.refresh()
historyModel.moveUp(result[1])
val testResult = historyModel.dataset.test()
.awaitValue()
.value()
assertEquals(result.size, testResult.size)
assertEquals(result[0], testResult[1])
assertEquals(result[1], testResult[0])
}
}

View File

@ -1,130 +1,97 @@
package org.videolan.vlc.viewmodels
import android.content.Context
import android.net.Uri
import android.text.TextUtils
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import com.jraska.livedata.test
import io.mockk.every
import io.mockk.slot
import io.mockk.spyk
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.setMain
import org.junit.Before
import org.junit.Rule
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.*
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.modules.junit4.PowerMockRunnerDelegate
import org.robolectric.RobolectricTestRunner
import org.videolan.libvlc.LibVLC
import org.videolan.medialibrary.MLServiceLocator
import org.videolan.medialibrary.Medialibrary
import org.videolan.medialibrary.interfaces.AbstractMedialibrary
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.medialibrary.media.MediaWrapper
import org.videolan.vlc.util.RoboLiteTestRunner
import org.videolan.medialibrary.stubs.StubDataSource
import org.videolan.vlc.BaseTest
import org.videolan.vlc.util.TestCoroutineContextProvider
import org.videolan.vlc.util.TestUtil
import org.videolan.vlc.util.mock
import java.io.File
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.powermock.modules.junit4.rule.PowerMockRule
import org.robolectric.annotation.Config
//@RunWith(RoboLiteTestRunner::class)
@RunWith(PowerMockRunner::class)
//@PowerMockRunnerDelegate(RobolectricTestRunner::class)
//@Config(sdk=[21])
@PrepareForTest(value = [Medialibrary::class, LibVLC::class, System::class, Uri::class, TextUtils::class])
@PowerMockIgnore(value = ["javax.management.*", "org.apache.http.conn.ssl.*", "com.amazonaws.http.conn.ssl.*", "javax.net.ssl.*", "androidx.*"])
class StreamsModelTest {
private val mockedLibrary: Medialibrary = PowerMockito.spy(Medialibrary())
private val context: Context = mock()
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
class StreamsModelTest : BaseTest() {
private val mediaLibrary: AbstractMedialibrary = AbstractMedialibrary.getInstance()
private lateinit var streamsModel: StreamsModel
// @Rule
// @JvmField
// val powerMockRule = PowerMockRule()
@Rule
@JvmField
//To prevent Method getMainLooper in android.os.Looper not mocked error when setting value for MutableLiveData
val instantExecutorRule = InstantTaskExecutorRule()
@ExperimentalCoroutinesApi
@Before
fun setUp() {
`when`(context.getExternalFilesDir(any())).thenReturn(File("./"))
`when`(context.getDir(any(), anyInt())).thenReturn(File("./"))
PowerMockito.mockStatic(Medialibrary::class.java)
PowerMockito.mockStatic(System::class.java)
PowerMockito.mockStatic(Uri::class.java)
PowerMockito.mockStatic(TextUtils::class.java)
PowerMockito.suppress(PowerMockito.method(System::class.java, "loadLibrary", String::class.java))
PowerMockito.`when`(Medialibrary.getInstance()).thenReturn(mockedLibrary)
Dispatchers.setMain(Dispatchers.Unconfined)
streamsModel = StreamsModel(context)
override fun beforeTest() {
super.beforeTest()
mediaLibrary.clearHistory()
streamsModel = StreamsModel(application, TestCoroutineContextProvider())
}
@Test
fun failedInitialization_GetEmptyCollection() {
PowerMockito.doReturn(Medialibrary.ML_INIT_FAILED).`when`(mockedLibrary, "nativeInit", any(), any())
mockedLibrary.init(context)
val testResult: MutableList<MediaWrapper> = mutableListOf()
streamsModel.refresh()
streamsModel.dataset
.test()
.awaitValue()
.assertHasValue()
.assertValue(testResult)
}
@Test
fun addTwoMediaHistory_GetTwoPlayedMediaStreams() {
// Setup
val mockedUri1: Uri = mock()
val mockedUri2: Uri = mock()
fun whenRefreshCalled_ListIsUpdated() {
val fakeMediaStrings = TestUtil.createNetworkUris(2)
`when`(Uri.parse(eq(fakeMediaStrings[0]))).thenReturn(mockedUri1)
`when`(mockedUri1.lastPathSegment).thenReturn(fakeMediaStrings[0])
`when`(mockedUri1.toString()).thenReturn(fakeMediaStrings[0])
`when`(Uri.parse(eq(fakeMediaStrings[1]))).thenReturn(mockedUri2)
`when`(mockedUri2.lastPathSegment).thenReturn(fakeMediaStrings[1])
`when`(mockedUri2.toString()).thenReturn(fakeMediaStrings[1])
streamsModel.dataset.test()
.awaitValue()
.assertValue(Medialibrary.EMPTY_COLLECTION.toMutableList())
val fakeMedias = fakeMediaStrings.map { s -> MediaWrapper(Uri.parse(s)) }
val result = fakeMedias.toTypedArray()
val result = fakeMediaStrings.map {
val media = MediaWrapper(Uri.parse(it))
println(mediaLibrary.addToHistory(media.location, media.title))
media
}
PowerMockito.doReturn(Medialibrary.ML_INIT_SUCCESS).`when`(mockedLibrary, "nativeInit", any(), any())
PowerMockito.doReturn(result).`when`(mockedLibrary, "nativeLastStreamsPlayed")
// Execution
mockedLibrary.init(context)
streamsModel.refresh()
// Assertions
val getResult = streamsModel.dataset
val testResult = streamsModel.dataset
.test()
.awaitValue()
.assertHasValue()
.value()
assertEquals(2, getResult.size)
assertEquals(result[0], getResult[0])
assertEquals(result[1], getResult[1])
assertEquals(result.size, testResult.size)
assertEquals(result[0], testResult[0])
assertEquals(result[1], testResult[1])
}
@Test
fun whenRenameCalledAtPos_MediaTitleIsUpdated() {
val pos = 0
val fakeMediaStrings = TestUtil.createNetworkUris(2)
val argumentName = slot<String>()
val result = fakeMediaStrings.map {
val media = spyk(MediaWrapper(Uri.parse(it)))
mediaLibrary.addToHistory(media.location, media.title)
media
}
val oldMediaTitle = result[pos].title
val media = result[pos]
every { media.rename(capture(argumentName)) } answers { media.setDisplayTitle(argumentName.captured) }
streamsModel.refresh()
streamsModel.dataset.test()
.awaitValue()
// .assertValue(result.toMutableList())
val newMediaTitle = "$oldMediaTitle~new"
streamsModel.rename(pos, newMediaTitle)
val testResult = streamsModel.dataset.test()
.awaitValue()
.assertHasValue()
.value()
assertEquals(newMediaTitle, testResult[pos].title)
}
}

View File

@ -0,0 +1,256 @@
package org.videolan.vlc.viewmodels
import android.net.Uri
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import com.jraska.livedata.test
import io.mockk.*
import io.mockk.impl.annotations.MockK
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.robolectric.RuntimeEnvironment
import org.videolan.vlc.BaseTest
import org.videolan.vlc.R
import org.videolan.vlc.api.NoConnectivityException
import org.videolan.vlc.database.ExternalSubDao
import org.videolan.vlc.database.models.ExternalSub
import org.videolan.vlc.gui.dialogs.State
import org.videolan.vlc.repository.ExternalSubRepository
import org.videolan.vlc.repository.OpenSubtitleRepository
import org.videolan.vlc.util.FileUtils
import org.videolan.vlc.util.TestCoroutineContextProvider
import org.videolan.vlc.util.TestUtil
import org.videolan.vlc.util.applyMock
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
class SubtitlesModelTest : BaseTest() {
@get:Rule
val temporaryFolder = TemporaryFolder()
@MockK
private lateinit var mockedOpenSubRepo: OpenSubtitleRepository
private val mockedDao: ExternalSubDao = mockk()
private lateinit var mediaPath: String
private val downloadedLiveData = MutableLiveData<List<ExternalSub>>()
private lateinit var subtitlesModel: SubtitlesModel
init {
val subRepo = ExternalSubRepository(mockedDao, TestCoroutineContextProvider())
ExternalSubRepository.applyMock(subRepo)
val capturedMedia = slot<String>()
// To mock the behavior of actual get function in DAO (filter by media path)
every { mockedDao.get(capture(capturedMedia)) } answers { Transformations.map(downloadedLiveData) { it.filter { it.mediaPath == capturedMedia.captured } } }
// To use the mocked instance of OpenSubRepository
OpenSubtitleRepository.instance = lazyOf(mockedOpenSubRepo)
// Used when computing hash of media file.
mockkObject(FileUtils) {
every { FileUtils.computeHash(any()) } returns "fake_hash"
}
}
override fun beforeTest() {
super.beforeTest()
mediaPath = temporaryFolder.newFile("fake_media").path
subtitlesModel = SubtitlesModel(application, Uri.parse(mediaPath), TestCoroutineContextProvider())
}
@Test
fun addFourDownloadedSubtitlesWithThreeCorrectMediaPath_checkHistoryHasSizeAsThree() {
val inputSubs = (0..2).map {
val subPath = temporaryFolder.newFile("sub$it").absolutePath
TestUtil.createExternalSub(it.toString(), subPath, mediaPath, "en", "xyz$it")
}.toMutableList()
val subPath = temporaryFolder.newFile("sub3").absolutePath
inputSubs.add(TestUtil.createExternalSub("3", subPath, "/wrong", "jp", "abc4"))
downloadedLiveData.value = inputSubs
val testResult = subtitlesModel.history.test()
.awaitValue()
.value()
assertEquals(3, testResult.size)
}
@Test
fun addThreeDownloadingSubtitlesWithTwoCorrectMediaPath_checkHistoryHasSizeAsTwo() {
(0..1).map {
ExternalSubRepository.getInstance(context).addDownloadingItem(
it.toLong(), TestUtil.createDownloadingSubtitleItem("$it", mediaPath, "en", "xyz", "abc.com/$it")
)
}
ExternalSubRepository.getInstance(context).addDownloadingItem(
2, TestUtil.createDownloadingSubtitleItem("2", "/wrong", "en", "xyz", "abc.com/2")
)
val testResult = subtitlesModel.history.test()
.awaitValue()
.value()
assertEquals(2, testResult.size)
}
@Test
fun addTwoDownloadingSubtitlesAndTwoDownloadedSubtitles_checkHistoryHasSizeAsFour() {
(0..1).map {
ExternalSubRepository.getInstance(context).addDownloadingItem(
it.toLong(), TestUtil.createDownloadingSubtitleItem("$it", mediaPath, "en", "xyz", "abc.com/$it")
)
}
val inputSubs = (0..1).map {
val subPath = temporaryFolder.newFile("sub$it").absolutePath
TestUtil.createExternalSub(it.toString(), subPath, mediaPath, "en", "xyz$it")
}.toList()
downloadedLiveData.value = inputSubs
val testResult = subtitlesModel.history.test()
.awaitValue()
.value()
assertEquals(4, testResult.size)
}
@Test
fun searchByNameAndNoResultHasFound_checkResultIsEmpty() {
coEvery { mockedOpenSubRepo.queryWithName(any(), any(), any(), any<List<String>>()) } returns emptyList()
subtitlesModel.observableSearchName.set("abc")
subtitlesModel.search(false)
subtitlesModel.result.test()
.awaitValue()
.assertValue { it.isEmpty() }
assertEquals(context.getString(R.string.no_result), subtitlesModel.observableMessage.get())
}
@Test
fun searchByNameAndTwoResultHasFound_checkResultHasSizeAsTwo() {
val openSubs = (0..1).map { TestUtil.createOpenSubtitle("$it", "en", "xyz", "abc.com/$it") }
coEvery { mockedOpenSubRepo.queryWithName(any(), any(), any(), any<List<String>>()) } returns openSubs
subtitlesModel.observableSearchName.set("abc")
subtitlesModel.search(false)
val testResult = subtitlesModel.result.test()
.awaitValue()
.value()
assertEquals(2, testResult.size)
assertEquals(testResult[0].state, State.NotDownloaded)
assertEquals(testResult[1].state, State.NotDownloaded)
}
@Test
fun searchByNameAndThreeResultHasFoundTwoAreInHistory_checkResultHasSizeAsThreeAndCorrectStates() {
val openSubs = (0..2).map { TestUtil.createOpenSubtitle("$it", "en", "xyz", "abc.com/$it") }
coEvery { mockedOpenSubRepo.queryWithName(any(), any(), any(), any<List<String>>()) } returns openSubs
ExternalSubRepository.getInstance(context).addDownloadingItem(
0L, TestUtil.createDownloadingSubtitleItem("0", mediaPath, "en", "xyz", "abc.com/0")
)
val subPath = temporaryFolder.newFile("sub1").absolutePath
downloadedLiveData.value = listOf(TestUtil.createExternalSub("1", subPath, mediaPath, "en", "xyz"))
subtitlesModel.observableSearchName.set("abc")
subtitlesModel.search(false)
subtitlesModel.history.test()
.awaitValue()
val testResult = subtitlesModel.result.test()
.awaitValue()
.value()
assertEquals(3, testResult.size)
assertEquals(testResult[0].state, State.Downloading)
assertEquals(testResult[1].state, State.Downloaded)
assertEquals(testResult[2].state, State.NotDownloaded)
}
@Test
fun addTwoDownloadedSubtitlesAndDeleteTwo_verifyDaoDeleteCalled() {
val inputSubs = (0..1).map {
val subPath = temporaryFolder.newFile("sub$it").absolutePath
TestUtil.createExternalSub(it.toString(), subPath, mediaPath, "en", "xyz$it")
}.toMutableList()
every { mockedDao.delete(any(), any()) } just runs
downloadedLiveData.value = inputSubs
(0..1).map { subtitlesModel.deleteSubtitle(mediaPath, "$it") }
verify(exactly = 2) { mockedDao.delete(any(), any()) }
}
@Test
fun searchByHashAndNoResultHasFound_checkResultIsEmpty() {
coEvery { mockedOpenSubRepo.queryWithHash(any(), any(), any<List<String>>()) } returns emptyList()
subtitlesModel.search(true)
subtitlesModel.result.test()
.awaitValue()
.assertValue { it.isEmpty() }
assertEquals(context.getString(R.string.no_result), subtitlesModel.observableMessage.get())
}
@Test
fun searchByHashAndTwoResultHasFound_checkResultHasSizeAsTwo() {
val openSubs = (0..1).map { TestUtil.createOpenSubtitle("$it", "en", "xyz", "abc.com/$it") }
coEvery { mockedOpenSubRepo.queryWithHash(any(), any(), any<List<String>>()) } returns openSubs
subtitlesModel.search(true)
val testResult = subtitlesModel.result.test()
.awaitValue()
.value()
assertEquals(2, testResult.size)
assertEquals(testResult[0].state, State.NotDownloaded)
assertEquals(testResult[1].state, State.NotDownloaded)
}
@Test
fun searchByNameWithNoConnection_checkValidMessage() {
coEvery { mockedOpenSubRepo.queryWithName(any(), any(), any(), any<List<String>>()) } throws NoConnectivityException()
subtitlesModel.observableSearchName.set("abc")
subtitlesModel.search(false)
subtitlesModel.result.test()
.awaitValue(3, TimeUnit.SECONDS)
.assertValue { it.isEmpty() }
assertEquals(context.getString(R.string.no_internet_connection), subtitlesModel.observableMessage.get())
}
@Test
fun searchByNameWithConverterError_checkValidMessage() {
coEvery { mockedOpenSubRepo.queryWithName(any(), any(), any(), any<List<String>>()) } throws IOException()
subtitlesModel.observableSearchName.set("abc")
subtitlesModel.search(false)
subtitlesModel.result.test()
.awaitValue(3, TimeUnit.SECONDS)
.assertValue { it.isEmpty() }
assertEquals(context.getString(R.string.some_error_occurred), subtitlesModel.observableMessage.get())
}
}

View File

@ -0,0 +1,173 @@
package org.videolan.vlc.viewmodels.browser
import android.net.Uri
import android.os.Handler
import androidx.lifecycle.MutableLiveData
import com.jraska.livedata.test
import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import junit.framework.Assert.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.videolan.libvlc.LibVLC
import org.videolan.libvlc.Media
import org.videolan.libvlc.test.TestMedia
import org.videolan.libvlc.util.MediaBrowser
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.vlc.BaseTest
import org.videolan.vlc.database.BrowserFavDao
import org.videolan.vlc.database.models.BrowserFav
import org.videolan.vlc.providers.BrowserProvider
import org.videolan.vlc.repository.BrowserFavRepository
import org.videolan.vlc.util.CoroutineContextProvider
import org.videolan.vlc.util.TestCoroutineContextProvider
import org.videolan.vlc.util.applyMock
import java.io.File
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
class FileBrowserModelTest : BaseTest() {
// Preferences choose directories to add in medialibrary scan.
@get:Rule
val temporaryFolder = TemporaryFolder()
private val mockedLibVlc: LibVLC = mockk(relaxed = true)
private lateinit var mediaBrowser: MediaBrowser
private val mockedFavoritesDao: BrowserFavDao = mockk(relaxed = true)
private val mockedFavoritesRepo: BrowserFavRepository = spyk(BrowserFavRepository(mockedFavoritesDao))
private lateinit var browserModel: BrowserModel
private lateinit var browserProvider: BrowserProvider
private val countVideos = 2
private val countDirs = 4
init {
BrowserFavRepository.applyMock(mockedFavoritesRepo)
// BrowserHandler mocked.
val handler: Handler = mockk()
BrowserProvider.overrideCreator = false
BrowserProvider.registerCreator {
this@FileBrowserModelTest.mediaBrowser = spyk(MediaBrowser(mockedLibVlc, it, handler))
mediaBrowser
}
BrowserProvider.registerCreator(clazz = CoroutineContextProvider::class.java) { TestCoroutineContextProvider() }
}
override fun beforeTest() {
super.beforeTest()
setupTestFiles()
}
private fun initBrowserModel(url: String?, showHiddenFiles: Boolean, showDummyCategory: Boolean = false) {
browserModel = BrowserModel(application, url, TYPE_FILE, showHiddenFiles, showDummyCategory, TestCoroutineContextProvider())
browserProvider = browserModel.provider
mediaBrowser = BrowserProvider.get(browserProvider)
}
private fun setupTestFiles() {
(1..countDirs).map { temporaryFolder.newFile("dir$it") }
(1..countVideos).map { temporaryFolder.newFile("video$it.mp4") }
}
private fun addFileToProvider(i: Int, file: File) {
val t = TestMedia(mockedLibVlc, "file://${file.path}").apply { if (!file.name.endsWith(".mp4")) type = Media.Type.Directory }
browserProvider.onMediaAdded(i, t)
}
private fun fillFilesInDataset(file: File) {
file.listFiles().sorted().mapIndexed(this::addFileToProvider)
browserProvider.onBrowseEnd()
}
private fun getFakeBrowserFav(index: Int): BrowserFav {
val t = temporaryFolder.newFile("fake_media$index")
return BrowserFav(Uri.parse(t.path), 0, "vid_$index", null)
}
@Test
fun whenAtRootAndInternalStorageIsEmpty_checkShowsFolderIsEmpty() {
initBrowserModel(null, showHiddenFiles = false)
val internalStorage = browserModel.dataset.test()
.awaitValue()
.value()[0]
// TODO Has to wait for browserChannel queue
Thread.sleep(1000)
browserProvider.onBrowseEnd()
Thread.sleep(1000)
assertTrue(browserModel.isFolderEmpty(internalStorage as AbstractMediaWrapper))
}
@Test
fun whenAtRootAndInternalStorageHasDirectories_checkShowsFolderIsNotEmpty() {
initBrowserModel(null, showHiddenFiles = false)
val internalStorage = browserModel.dataset.test()
.awaitValue()
.value()[0]
Thread.sleep(1000)
// TODO Hack because parseSubDirectories is called twice for some reason.
// browserProvider.onBrowseEnd()
fillFilesInDataset(temporaryFolder.root)
Thread.sleep(1000)
assertFalse(browserModel.isFolderEmpty(internalStorage as AbstractMediaWrapper))
}
@Test
fun whenAtRootAndSavedList_checkPrefetchListIsFilled() {
initBrowserModel(null, showHiddenFiles = false)
val internalStorage = browserModel.dataset.test()
.awaitValue()
.value()[0]
Thread.sleep(1000)
// browserProvider.onBrowseEnd()
fillFilesInDataset(temporaryFolder.root)
Thread.sleep(1000)
browserModel.saveList(internalStorage as AbstractMediaWrapper)
initBrowserModel(internalStorage.uri.toString(), false)
val testResult = browserModel.dataset.test()
.value()
assertEquals(countDirs + countVideos, testResult.size)
}
@Test
fun whenAtRootAndHasLocalFavorite_checkDataSetContainsIt() {
val liveFavorites: MutableLiveData<List<BrowserFav>> = MutableLiveData()
every { mockedFavoritesRepo.localFavorites } returns liveFavorites
initBrowserModel(null, showHiddenFiles = false)
val noFav = browserModel.dataset.test()
.awaitValue()
.value()
assertEquals(1, noFav.size)
liveFavorites.value = listOf(getFakeBrowserFav(0))
val hasFav = browserModel.dataset.test()
.awaitValue()
.value()
assertEquals(3, hasFav.size)
}
}

View File

@ -0,0 +1,125 @@
package org.videolan.vlc.viewmodels.browser
import android.os.Handler
import com.jraska.livedata.test
import io.mockk.mockk
import io.mockk.spyk
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.videolan.libvlc.LibVLC
import org.videolan.libvlc.Media
import org.videolan.libvlc.test.TestMedia
import org.videolan.libvlc.util.MediaBrowser
import org.videolan.vlc.BaseTest
import org.videolan.vlc.providers.BrowserProvider
import org.videolan.vlc.util.CoroutineContextProvider
import org.videolan.vlc.util.TestCoroutineContextProvider
import java.io.File
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
class FilePickerModelTest : BaseTest() {
@get:Rule
val temporaryFolder = TemporaryFolder()
private val mockedLibVlc: LibVLC = mockk(relaxed = true)
private lateinit var dummyUrl: String
private lateinit var mediaBrowser: MediaBrowser
private lateinit var browserModel: BrowserModel
private lateinit var browserProvider: BrowserProvider
private val countVideos = 2
private val countDirs = 4
init {
// BrowserHandler mocked.
val handler: Handler = mockk()
BrowserProvider.overrideCreator = false
BrowserProvider.registerCreator {
mediaBrowser = spyk(MediaBrowser(mockedLibVlc, it, handler))
mediaBrowser
}
BrowserProvider.registerCreator(clazz = CoroutineContextProvider::class.java) { TestCoroutineContextProvider() }
}
override fun beforeTest() {
super.beforeTest()
dummyUrl = temporaryFolder.root.absolutePath
browserModel = BrowserModel(application, dummyUrl, TYPE_PICKER, false, true, TestCoroutineContextProvider())
browserProvider = browserModel.provider
setupTestFiles()
}
private fun setupTestFiles() {
(1..countDirs).map { temporaryFolder.newFile("dir$it") }
(1..countVideos).map { temporaryFolder.newFile("video$it.mp4") }
}
private fun addFileToProvider(i: Int, file: File) {
val t = TestMedia(mockedLibVlc, "file://${file.path}").apply { if (!file.name.endsWith(".mp4")) type = Media.Type.Directory }
browserProvider.onMediaAdded(i, t)
}
@Test
fun whenBrowseRootAndRootHasFiles_getListOfDirectories() {
temporaryFolder.root.listFiles().mapIndexed(this::addFileToProvider)
browserProvider.onBrowseEnd()
val testResult = browserModel.dataset.test()
.value()
assertEquals(countDirs, testResult.size)
assert(testResult[0].title.startsWith("dir"))
}
@Test
fun whenBrowseRootAndRootEmpty_checkTheFolderIsEmpty() {
browserProvider.onBrowseEnd()
browserModel.dataset.test()
.assertValue { it.isEmpty() }
}
@Test
fun whenBrowseRootAndBrowseEndEventTriggered_ensureLoadingIsSetToFalse() {
browserModel.loading.test()
.assertValue(true)
browserProvider.onBrowseEnd()
browserModel.loading.test()
.awaitValue()
.assertValue(false)
}
@Test
fun whenRootHasFilesAndRefreshCalled_getUpdatedListOfDirectories() {
temporaryFolder.root.listFiles().mapIndexed(this::addFileToProvider)
browserProvider.onBrowseEnd()
var result = browserModel.dataset.test()
.value()
assertEquals(countDirs, result.size)
browserModel.refresh()
temporaryFolder.newFile("dir${countDirs + 1}")
temporaryFolder.root.listFiles().mapIndexed(this::addFileToProvider)
browserProvider.onBrowseEnd()
result = browserModel.dataset.test()
.value()
// TODO: This will fail because the refresh behavior is buggy of BrowserProvider subclasses.
assertEquals(countDirs + 1, result.size)
}
}

View File

@ -0,0 +1,110 @@
package org.videolan.vlc.viewmodels.browser
import android.net.Uri
import android.os.Handler
import androidx.lifecycle.MediatorLiveData
import com.jraska.livedata.test
import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.videolan.libvlc.LibVLC
import org.videolan.libvlc.util.MediaBrowser
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.medialibrary.media.MediaWrapper
import org.videolan.vlc.BaseTest
import org.videolan.vlc.database.BrowserFavDao
import org.videolan.vlc.providers.BrowserProvider
import org.videolan.vlc.repository.BrowserFavRepository
import org.videolan.vlc.util.CoroutineContextProvider
import org.videolan.vlc.util.Settings
import org.videolan.vlc.util.TestCoroutineContextProvider
import org.videolan.vlc.util.applyMock
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
class NetworkModelTest : BaseTest() {
@get:Rule
val temporaryFolder = TemporaryFolder()
private val mockedLibVlc: LibVLC = mockk(relaxed = true)
private val mockedFavoritesDao: BrowserFavDao = mockk(relaxed = true)
private val mockedFavoritesRepo: BrowserFavRepository = spyk(BrowserFavRepository(mockedFavoritesDao))
private lateinit var mediaBrowser: MediaBrowser
private lateinit var browserModel: BrowserModel
private lateinit var browserProvider: BrowserProvider
private val countVideos = 2
private val countDirs = 4
init {
BrowserFavRepository.applyMock(mockedFavoritesRepo)
// BrowserHandler mocked.
val handler: Handler = mockk()
BrowserProvider.overrideCreator = false
BrowserProvider.registerCreator {
mediaBrowser = spyk(MediaBrowser(mockedLibVlc, it, handler))
mediaBrowser
}
BrowserProvider.registerCreator(clazz = CoroutineContextProvider::class.java) { TestCoroutineContextProvider() }
}
private fun initNetworkModel(url: String?, showHiddenFiles: Boolean = false) {
browserModel = NetworkModel(application, url, showHiddenFiles, TestCoroutineContextProvider())
browserProvider = browserModel.provider
}
private fun getFakeMediaWrapper(index: Int): MediaWrapper = MediaWrapper(Uri.parse("http://fake_media.io/vid_$index.mp4"))
@Test
fun whenAtRootAndNoFavorites_checkResultIsEmpty() {
Settings.overrideTvUI = true
every { mockedFavoritesRepo.networkFavorites } returns MediatorLiveData<List<AbstractMediaWrapper>>().apply { value = emptyList() }
initNetworkModel(null)
val testResult = browserModel.dataset.test()
.awaitValue()
.value()
assertEquals(0, testResult.size)
}
@Test
fun whenAtRootWithOneFavoriteAndOneFavoriteAddedLater_checkResultIsNotEmptyAndContainsThem() {
val liveFavorites: MediatorLiveData<List<AbstractMediaWrapper>> = MediatorLiveData()
Settings.overrideTvUI = true
every { mockedFavoritesRepo.networkFavorites } returns liveFavorites
liveFavorites.value = listOf(getFakeMediaWrapper(0).apply { setStateFlags(MediaLibraryItem.FLAG_FAVORITE) })
initNetworkModel(null)
val oldResult = browserModel.dataset.test()
.awaitValue()
.value()
assertEquals(3, oldResult.size)
assertEquals(getFakeMediaWrapper(0), oldResult[1])
liveFavorites.value = ArrayList<AbstractMediaWrapper>().apply {
liveFavorites.value?.let { addAll(it) }
add(getFakeMediaWrapper(1).apply { setStateFlags(MediaLibraryItem.FLAG_FAVORITE) })
}
val newResult = browserModel.dataset.test()
.awaitValue()
.value()
assertEquals(4, newResult.size)
assertEquals(getFakeMediaWrapper(0), newResult[1])
assertEquals(getFakeMediaWrapper(1), newResult[2])
}
}

View File

@ -0,0 +1,180 @@
package org.videolan.vlc.viewmodels.browser
import android.os.Environment
import android.os.Handler
import com.jraska.livedata.test
import io.mockk.coEvery
import io.mockk.mockk
import io.mockk.spyk
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.junit.Assert.assertNotEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.videolan.libvlc.LibVLC
import org.videolan.libvlc.Media
import org.videolan.libvlc.test.TestMedia
import org.videolan.libvlc.util.MediaBrowser
import org.videolan.medialibrary.Medialibrary
import org.videolan.vlc.BaseTest
import org.videolan.vlc.R
import org.videolan.vlc.database.CustomDirectoryDao
import org.videolan.vlc.database.models.CustomDirectory
import org.videolan.vlc.providers.BrowserProvider
import org.videolan.vlc.repository.DirectoryRepository
import org.videolan.vlc.util.CoroutineContextProvider
import org.videolan.vlc.util.TestCoroutineContextProvider
import org.videolan.vlc.util.applyMock
import java.io.File
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
class StorageModelTest : BaseTest() {
// Preferences choose directories to add in medialibrary scan.
@get:Rule
val temporaryFolder = TemporaryFolder()
private val mockedLibVlc: LibVLC = mockk(relaxed = true)
private val mockedDirectoryDao: CustomDirectoryDao = mockk()
private val mockedDirectoryRepo: DirectoryRepository = spyk(DirectoryRepository(mockedDirectoryDao))
private lateinit var mediaBrowser: MediaBrowser
private lateinit var browserModel: BrowserModel
private lateinit var browserProvider: BrowserProvider
private var showHiddenFiles: Boolean = false
private val countVideos = 2
private val countDirs = 4
private val countHiddenDirs = 2
init {
DirectoryRepository.applyMock(mockedDirectoryRepo)
// BrowserHandler mocked.
val handler: Handler = mockk()
BrowserProvider.overrideCreator = false
BrowserProvider.registerCreator {
mediaBrowser = spyk(MediaBrowser(mockedLibVlc, it, handler))
mediaBrowser
}
BrowserProvider.registerCreator(clazz = CoroutineContextProvider::class.java) { TestCoroutineContextProvider() }
}
override fun beforeTest() {
super.beforeTest()
setupTestFiles()
}
/**
* Setups the browser model.
*
* @param [showHiddenFiles] - whether to show hidden directories too when browsing.
* @param [url] - URL to browse. null for root.
*
*/
private fun initBrowserModel(showHiddenFiles: Boolean, url: String?) {
this.showHiddenFiles = showHiddenFiles
browserModel = BrowserModel(application, url, TYPE_STORAGE, showHiddenFiles, false, TestCoroutineContextProvider())
browserProvider = browserModel.provider
}
private fun setupTestFiles() {
(1..countDirs).map { temporaryFolder.newFile("dir$it") }
(1..countHiddenDirs).map { temporaryFolder.newFile(".hiddenDir$it") }
(1..countVideos).map { temporaryFolder.newFile("video$it.mp4") }
}
private fun addFileToProvider(i: Int, file: File) {
if (file.name.startsWith(".") && !showHiddenFiles)
return
val t = TestMedia(mockedLibVlc, "file://${file.path}").apply { if (!file.name.endsWith(".mp4")) type = Media.Type.Directory }
browserProvider.onMediaAdded(i, t)
}
private fun fillFilesInDataset(file: File) {
file.listFiles().sorted().mapIndexed(this::addFileToProvider)
browserProvider.onBrowseEnd()
}
@Test
fun whenAtRootAndNoCustomDirectory_checkOnlyInternalStorageShows() {
initBrowserModel(false, null)
val testResult = browserModel.dataset.test()
.awaitValue()
.value()
assertEquals(1, testResult.size)
assertEquals(context.getString(R.string.internal_memory), testResult[0].title)
}
@Test
fun whenAtRootAndTwoCustomDirectoriesWithOneChildOfInternalStorage_checkTwoResultsAreObtained() {
val customDir = CustomDirectory(temporaryFolder.newFile("custom1").path)
val newDirInsideInternalStorage = File("${Environment.getExternalStorageDirectory().path}/custom2")
newDirInsideInternalStorage.mkdir()
val customDirInsideInternalStorage = CustomDirectory(newDirInsideInternalStorage.path)
coEvery { mockedDirectoryRepo.getCustomDirectories() } returns listOf(customDir, customDirInsideInternalStorage)
initBrowserModel(false, null)
// TODO: This test will fail because nested directories should not be shown.
val testResult = browserModel.dataset.test()
.awaitValue()
.value()
assertEquals(2, testResult.size)
assertEquals(context.getString(R.string.internal_memory), testResult[0].title)
}
@Test
fun whenAtCustomDirAndHiddenDirectoryPresentWithHideHiddenFiles_checkResultHasCorrectDirectoriesAndFlagIsNotShowHiddenFiles() {
val customDir = CustomDirectory(temporaryFolder.root.path)
coEvery { mockedDirectoryRepo.getCustomDirectories() } returns listOf(customDir)
initBrowserModel(false, customDir.path)
fillFilesInDataset(temporaryFolder.root)
val testResult = browserModel.dataset.test()
.value()
assertEquals(countDirs, testResult.size)
assertEquals(0, browserProvider.getFlags() and MediaBrowser.Flag.ShowHiddenFiles)
}
@Test
fun whenAtCustomDirAndHiddenDirectoryPresentWithShowHiddenFiles_checkResultHasHiddenDirectoriesAndFlagIsShowHiddenFiles() {
val customDir = CustomDirectory(temporaryFolder.root.path)
coEvery { mockedDirectoryRepo.getCustomDirectories() } returns listOf(customDir)
initBrowserModel(true, customDir.path)
fillFilesInDataset(temporaryFolder.root)
val testResult = browserModel.dataset.test()
.value()
assertEquals(countDirs + countHiddenDirs, testResult.size)
assertNotEquals(0, browserProvider.getFlags() and MediaBrowser.Flag.ShowHiddenFiles)
}
@Test
fun whenAtInternalStorageAndSortedDescending_checkResultIsReversed() {
initBrowserModel(false, Environment.getExternalStorageDirectory().path)
fillFilesInDataset(temporaryFolder.root)
val oldResult = browserModel.dataset.test()
.value().toList()
browserModel.sort(Medialibrary.SORT_ALPHA)
val newResult = browserModel.dataset.test()
.awaitValue()
.value()
assertEquals(oldResult.reversed(), newResult)
}
}

View File

@ -0,0 +1,117 @@
package org.videolan.vlc.viewmodels.mobile
import com.jraska.livedata.test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.videolan.medialibrary.interfaces.media.AbstractFolder
import org.videolan.medialibrary.stubs.StubDataSource
import org.videolan.vlc.BaseTest
import org.videolan.vlc.util.MEDIALIBRARY_PAGE_SIZE
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
class VideosViewModelTest : BaseTest() {
private lateinit var videosViewModel: VideosViewModel
override fun beforeTest() {
super.beforeTest()
StubDataSource.getInstance().resetData()
}
private fun setupViewModel(folder: AbstractFolder?) {
videosViewModel = VideosViewModel(context, application, folder)
}
@Test
fun whenFolderIsNull_checkResultIsEmpty() {
setupViewModel(null)
videosViewModel.provider.pagedList.test()
.awaitValue()
assertTrue(videosViewModel.isEmpty())
}
@Test
fun whenFolderIsNullAndMediaLibraryHasPagedVideos_checkResultContainsThem() {
val videoCount = 2
setupViewModel(null)
StubDataSource.getInstance().setVideoByCount(videoCount, null)
val testResult = videosViewModel.provider.pagedList.test()
.awaitValue().value()
assertEquals(2, testResult.size)
}
@Test
fun whenFolderIsGivenAndItIsEmpty_checkResultIsEmpty() {
setupViewModel(StubDataSource.getInstance().createFolder("test"))
videosViewModel.provider.pagedList.test()
.awaitValue()
assertTrue(videosViewModel.isEmpty())
}
@Test
fun whenFolderIsGivenAndItHasVideosAndAudio_checkPagedListContainsOnlyVideos() {
val videoCount = 2
val audioCount = 1
setupViewModel(StubDataSource.getInstance().createFolder("test"))
StubDataSource.getInstance().setVideoByCount(videoCount, "test")
StubDataSource.getInstance().setAudioByCount(audioCount, "test")
val testResult = videosViewModel.provider.pagedList.test()
.awaitValue().value()
assertEquals(2, testResult.size)
}
@Test
fun whenFolderIsGivenAndItHasVideosAndAudio_checkTotalCountHasOnlyVideos() {
val videoCount = 2
val audioCount = 1
setupViewModel(StubDataSource.getInstance().createFolder("test"))
StubDataSource.getInstance().setVideoByCount(videoCount, "test")
StubDataSource.getInstance().setAudioByCount(audioCount, "test")
videosViewModel.provider.pagedList.test()
.awaitValue()
assertEquals(2, videosViewModel.provider.getTotalCount())
}
@Test
fun whenFolderIsNullAndVideosAreMoreThanMaxSize_checkLastIsNotLoadedYet() {
val videoCount = MEDIALIBRARY_PAGE_SIZE * 3 + 1
setupViewModel(null)
StubDataSource.getInstance().setVideoByCount(videoCount, "test")
val testResult = videosViewModel.provider.pagedList.test()
.awaitValue()
.value()
assertEquals(null, testResult[videoCount - 1])
}
@Test
fun whenFolderIsNullAndItHasVideos_checkGetAllReturnsAll() {
val videoCount = MEDIALIBRARY_PAGE_SIZE + 1
val audioCount = 200
setupViewModel(StubDataSource.getInstance().createFolder("test"))
StubDataSource.getInstance().setVideoByCount(videoCount, "test")
StubDataSource.getInstance().setAudioByCount(audioCount, "test")
val testResult = videosViewModel.provider.getAll()
assertEquals(videoCount, testResult.size)
}
}