Commit 4184032c authored by Geoffrey Métais's avatar Geoffrey Métais

Chromecast Implementation

parent d29241e3
......@@ -21,6 +21,15 @@
android:shadowDy="3"
android:shadowRadius="1.5" />
<ImageView
android:id="@+id/video_renderer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_am_renderer_normal_w"
android:focusable="true"
android:visibility="gone"/>
<ImageView
android:id="@+id/playlist_toggle"
android:layout_width="wrap_content"
......
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="org.videolan.libvlc.RendererItem" />
<variable
name="holder"
type="org.videolan.vlc.gui.dialogs.RenderersDialog.RendererClickhandler" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="448dp">
<TextView
android:id="@+id/renderers_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:text="@string/renderer_list_title"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:padding="16dp" />
<android.support.v7.widget.RecyclerView
android:id="@+id/renderers_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/renderers_title"
tools:listitem="@layout/item_renderer"/>
<Button
android:id="@+id/renderers_disconnect"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/renderers_list"
app:layout_constraintBottom_toBottomOf="parent"
android:text="Disconnect"
tools:targetApi="11"
style="?android:attr/borderlessButtonStyle"
android:textColor="@color/orange800"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:onClick="@{() -> holder.connect((RendererItem)null)}" />
</android.support.constraint.ConstraintLayout>
</layout>
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="renderer"
type="org.videolan.libvlc.RendererItem" />
<variable
name="clicHandler"
type="org.videolan.vlc.gui.dialogs.RenderersDialog.RendererClickhandler" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp">
<TextView
android:id="@+id/renderer_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{renderer.displayName}"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:clickable="true"
android:focusable="true"
android:onClick="@{() -> clicHandler.connect(renderer)}"/>
</LinearLayout>
</layout>
\ No newline at end of file
......@@ -17,6 +17,15 @@
android:textColor="@color/white"
android:textSize="15sp" />
<ImageView
android:id="@+id/video_renderer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_am_renderer_normal_w"
android:focusable="true"
android:visibility="gone"/>
<TextView
android:id="@+id/player_overlay_battery"
android:layout_width="wrap_content"
......
......@@ -2,6 +2,14 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:vlc="http://schemas.android.com/apk/res-auto" >
<item
android:orderInCategory="1"
android:id="@+id/ml_menu_renderers"
android:icon="@drawable/ic_am_renderer_normal_w"
android:title="@string/searchable_hint"
vlc:showAsAction="always|collapseActionView"
android:visible="false"/>
<item
android:orderInCategory="1"
android:id="@+id/ml_menu_filter"
......@@ -14,14 +22,14 @@
android:id="@+id/ml_menu_last_playlist"
android:icon="@drawable/ic_menu_lastplaylist"
android:title="@string/last_playlist"
vlc:showAsAction="ifRoom"
vlc:showAsAction="always|collapseActionView"
android:visible="false"/>
<item
android:orderInCategory="2"
android:title="@string/sortby"
android:icon="@drawable/ic_menu_sort"
android:id="@+id/ml_menu_sortby"
vlc:showAsAction="ifRoom"
vlc:showAsAction="always|collapseActionView"
android:visible="false">
<menu>
<item
......
......@@ -574,4 +574,5 @@
<string name="browser_quick_access">Quick Access</string>
<string name="browser_storages">Storages</string>
<string name="msg_delete_failed">Failed to delete media %1$s</string>
<string name="renderer_list_title">Displays</string>
</resources>
......@@ -56,7 +56,7 @@ import java.util.List;
public class ExternalMonitor extends BroadcastReceiver {
public final static String TAG = "VLC/ExternalMonitor";
private static volatile boolean connected = true;
private static volatile boolean connected = false;
private static volatile boolean mobile = true;
private static volatile boolean vpn = false;
private static final ExternalMonitor instance = new ExternalMonitor();
......@@ -103,7 +103,7 @@ public class ExternalMonitor extends BroadcastReceiver {
cm = (ConnectivityManager) VLCApplication.getAppContext().getSystemService(
Context.CONNECTIVITY_SERVICE);
final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
final boolean isConnected = networkInfo != null && networkInfo.isConnectedOrConnecting();
final boolean isConnected = networkInfo != null && networkInfo.isConnected();
mobile = isConnected && networkInfo.getType() == ConnectivityManager.TYPE_MOBILE;
vpn = isConnected && updateVPNStatus();
if (isConnected != connected) {
......
......@@ -72,6 +72,7 @@ import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaList;
import org.videolan.libvlc.MediaPlayer;
import org.videolan.libvlc.RendererItem;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.medialibrary.Medialibrary;
import org.videolan.medialibrary.Tools;
......@@ -2078,7 +2079,8 @@ public class PlaybackService extends MediaBrowserServiceCompat implements IVLCVo
if (mw.hasFlag(MediaWrapper.MEDIA_FORCE_AUDIO) && mMediaPlayer.getAudioTracksCount() == 0)
next();
else if (mw .getType() != MediaWrapper.TYPE_VIDEO || isVideoPlaying || mw.hasFlag(MediaWrapper.MEDIA_FORCE_AUDIO)) {
else if (mw .getType() != MediaWrapper.TYPE_VIDEO || isVideoPlaying || mw.hasFlag(MediaWrapper.MEDIA_FORCE_AUDIO)
|| RendererDelegate.INSTANCE.getSelectedRenderer() != null) {
mMediaPlayer.setEqualizer(VLCOptions.getEqualizerSetFromSettings(this));
mMediaPlayer.setVideoTitleDisplay(MediaPlayer.Position.Disable, 0);
changeAudioFocus(true);
......@@ -2546,6 +2548,11 @@ public class PlaybackService extends MediaBrowserServiceCompat implements IVLCVo
return mMediaPlayer.setSpuDelay(delay);
}
@MainThread
public int setRenderer(RendererItem item) {
return mMediaPlayer.setRenderer(item);
}
@MainThread
public long getSpuDelay() {
return mMediaPlayer.getSpuDelay();
......@@ -2603,6 +2610,7 @@ public class PlaybackService extends MediaBrowserServiceCompat implements IVLCVo
mMediaPlayer.getVLCVout().detachViews();
final MediaPlayer mp = mMediaPlayer;
mMediaPlayer = newMediaPlayer();
mMediaPlayer.setRenderer(RendererDelegate.INSTANCE.getSelectedRenderer());
VLCApplication.runBackground(new Runnable() {
@Override
public void run() {
......
/*****************************************************************************
* RendererDelegate.java
*
* Copyright © 2017 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*/
package org.videolan.vlc
import org.videolan.libvlc.RendererDiscoverer
import org.videolan.libvlc.RendererItem
import org.videolan.vlc.util.VLCInstance
import java.util.*
object RendererDelegate : RendererDiscoverer.EventListener, ExternalMonitor.NetworkObserver {
private val TAG = "VLC/RendererDelegate"
private val mDiscoverers = ArrayList<RendererDiscoverer>()
val renderers = ArrayList<RendererItem>()
private val mListeners = LinkedList<RendererListener>()
private val mPlayers = LinkedList<RendererPlayer>()
@Volatile private var started = false
var selectedRenderer: RendererItem? = null
private set
init {
ExternalMonitor.subscribeNetworkCb(this)
}
interface RendererListener {
fun onRenderersChanged(empty: Boolean)
}
interface RendererPlayer {
fun onRendererChanged(renderer: RendererItem?)
}
fun start() {
if (started) return
started = true
val libVlc = VLCInstance.get()
for (discoverer in RendererDiscoverer.list(libVlc)) {
val rd = RendererDiscoverer(libVlc, discoverer.name)
mDiscoverers.add(rd)
rd.setEventListener(this)
rd.start()
}
}
fun stop() {
if (!started) return
started = false
for (discoverer in mDiscoverers) discoverer.stop()
clear()
onRenderersChanged()
for (player in mPlayers) player.onRendererChanged(null)
}
private fun clear() {
mDiscoverers.clear()
for (renderer in renderers) renderer.release()
renderers.clear()
}
override fun onNetworkConnectionChanged(connected: Boolean) = if (connected) start() else stop()
override fun onEvent(event: RendererDiscoverer.Event?) {
when (event?.type) {
RendererDiscoverer.Event.ItemAdded -> { renderers.add(event.item) }
RendererDiscoverer.Event.ItemDeleted -> { renderers.remove(event.item); event.item.release() }
else -> return
}
onRenderersChanged()
}
fun addListener(listener: RendererListener) = mListeners.add(listener)
fun removeListener(listener: RendererListener) = mListeners.remove(listener)
private fun onRenderersChanged() {
for (listener in mListeners) listener.onRenderersChanged(renderers.isEmpty())
}
fun selectRenderer(item: RendererItem?) {
selectedRenderer = item
for (player in mPlayers) player.onRendererChanged(item)
}
fun addPlayerListener(listener: RendererPlayer) = mPlayers.add(listener)
fun removePlayerListener(listener: RendererPlayer) = mPlayers.remove(listener)
}
......@@ -6,6 +6,7 @@ import android.support.v7.widget.RecyclerView;
import org.jetbrains.annotations.NotNull;
import org.videolan.medialibrary.media.MediaLibraryItem;
import org.videolan.vlc.gui.DiffUtilAdapter;
import org.videolan.vlc.util.MediaItemDiffCallback;
import org.videolan.vlc.util.MediaLibraryItemComparator;
import org.videolan.vlc.util.Util;
......@@ -73,7 +74,6 @@ public abstract class SortableAdapter<T extends MediaLibraryItem, VH extends Rec
@SuppressWarnings("unchecked")
@NonNull
@NotNull
@Override
protected List<T> prepareList(@NotNull List<? extends T> list) {
if (needsSorting()) Collections.sort(list, sMediaComparator);
......
......@@ -23,9 +23,11 @@ import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
......@@ -290,8 +292,7 @@ public class VLCApplication extends Application {
@Override
public void onActivityStarted(Activity activity) {
if (++sActivitiesCount == 1)
ExternalMonitor.register(instance);
if (++sActivitiesCount == 1) ExternalMonitor.register(instance);
}
@Override
......@@ -302,8 +303,7 @@ public class VLCApplication extends Application {
@Override
public void onActivityStopped(Activity activity) {
if (--sActivitiesCount == 0)
ExternalMonitor.unregister(instance);
if (--sActivitiesCount == 0) ExternalMonitor.unregister(instance);
}
@Override
......
......@@ -23,7 +23,6 @@
package org.videolan.vlc.gui;
import android.app.SearchManager;
import android.content.ClipData;
import android.content.Intent;
......@@ -39,19 +38,23 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import org.videolan.libvlc.RendererItem;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.medialibrary.media.MediaWrapper;
import org.videolan.vlc.R;
import org.videolan.vlc.RendererDelegate;
import org.videolan.vlc.gui.audio.EqualizerFragment;
import org.videolan.vlc.gui.browser.ExtensionBrowser;
import org.videolan.vlc.gui.dialogs.RenderersDialog;
import org.videolan.vlc.interfaces.Filterable;
import org.videolan.vlc.media.MediaUtils;
public class ContentActivity extends AudioPlayerContainerActivity implements SearchView.OnQueryTextListener, MenuItemCompat.OnActionExpandListener {
public class ContentActivity extends AudioPlayerContainerActivity implements SearchView.OnQueryTextListener, MenuItemCompat.OnActionExpandListener, RendererDelegate.RendererListener, RendererDelegate.RendererPlayer {
public static final String TAG = "VLC/ContentActivity";
protected Menu mMenu;
private SearchView mSearchView;
private boolean showRenderers = !RendererDelegate.INSTANCE.getRenderers().isEmpty();
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
......@@ -108,9 +111,25 @@ public class ContentActivity extends AudioPlayerContainerActivity implements Sea
}
else
menu.findItem(R.id.ml_menu_filter).setVisible(false);
menu.findItem(R.id.ml_menu_renderers).setVisible(showRenderers);
menu.findItem(R.id.ml_menu_renderers).setIcon(RendererDelegate.INSTANCE.getSelectedRenderer() == null ? R.drawable.ic_am_renderer_normal_w : R.drawable.ic_am_renderer_on_w);
return super.onCreateOptionsMenu(menu);
}
@Override
protected void onStart() {
super.onStart();
RendererDelegate.INSTANCE.addListener(this);
RendererDelegate.INSTANCE.addPlayerListener(this);
}
@Override
protected void onStop() {
super.onStop();
RendererDelegate.INSTANCE.removeListener(this);
RendererDelegate.INSTANCE.removePlayerListener(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
......@@ -122,6 +141,10 @@ public class ContentActivity extends AudioPlayerContainerActivity implements Sea
case R.id.ml_menu_search:
startActivity(new Intent(Intent.ACTION_SEARCH, null, this, SearchActivity.class));
return true;
case R.id.ml_menu_renderers:
if (getSupportFragmentManager().findFragmentByTag("renderers") == null)
new RenderersDialog().show(getSupportFragmentManager(), "renderers");
return true;
default:
return super.onOptionsItemSelected(item);
}
......@@ -186,4 +209,15 @@ public class ContentActivity extends AudioPlayerContainerActivity implements Sea
((Filterable) current).restoreList();
}
}
@Override
public void onRenderersChanged(boolean empty) {
showRenderers = !empty;
supportInvalidateOptionsMenu();
}
@Override
public void onRendererChanged(@Nullable RendererItem renderer) {
supportInvalidateOptionsMenu();
}
}
......@@ -3,16 +3,15 @@ package org.videolan.vlc.gui
import android.support.annotation.WorkerThread
import android.support.v7.util.DiffUtil
import android.support.v7.widget.RecyclerView
import android.util.Log
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.channels.actor
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.newSingleThreadContext
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.vlc.util.MediaItemDiffCallback
import java.util.*
abstract class DiffUtilAdapter<D : MediaLibraryItem, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
abstract class DiffUtilAdapter<D, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
protected var dataset: List<D> = listOf()
@Volatile private var last = dataset
......@@ -29,8 +28,9 @@ abstract class DiffUtilAdapter<D : MediaLibraryItem, VH : RecyclerView.ViewHolde
@WorkerThread
private suspend fun internalUpdate(list: List<D>) {
Log.d("dua", "old list ${dataset.size} -> ${list.size}")
val finalList = prepareList(list)
val result = DiffUtil.calculateDiff(diffCallback.apply { this.update(dataset, finalList) }, detectMoves())
val result = DiffUtil.calculateDiff(diffCallback.apply { update(dataset, finalList) }, detectMoves())
launch(UI) {
dataset = finalList
result.dispatchUpdatesTo(this@DiffUtilAdapter)
......@@ -46,5 +46,23 @@ abstract class DiffUtilAdapter<D : MediaLibraryItem, VH : RecyclerView.ViewHolde
open protected fun detectMoves() = false
open protected fun createCB() = MediaItemDiffCallback<D>()
open protected fun createCB() = DiffCallback<D>()
open class DiffCallback<D> : DiffUtil.Callback() {
lateinit var oldList: List<D>
lateinit var newList: List<D>
fun update(oldList: List<D>, newList: List<D>) {
this.oldList = oldList
this.newList = newList
}
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areContentsTheSame(oldItemPosition : Int, newItemPosition : Int) = true
override fun areItemsTheSame(oldItemPosition : Int, newItemPosition : Int) = oldList[oldItemPosition] == newList[newItemPosition]
}
}
\ No newline at end of file
......@@ -66,6 +66,7 @@ import org.videolan.vlc.gui.browser.ExtensionBrowser;
import org.videolan.vlc.gui.browser.FileBrowserFragment;
import org.videolan.vlc.gui.browser.MediaBrowserFragment;
import org.videolan.vlc.gui.browser.NetworkBrowserFragment;
import org.videolan.vlc.gui.dialogs.RenderersDialog;
import org.videolan.vlc.gui.helpers.UiTools;
import org.videolan.vlc.gui.network.MRLPanelFragment;
import org.videolan.vlc.gui.preferences.PreferencesActivity;
......@@ -243,6 +244,12 @@ public class MainActivity extends ContentActivity implements FilterQueryProvider
mNavigationView.setNavigationItemSelectedListener(this);
if (BuildConfig.DEBUG)
createExtensionServiceConnection();
// mActivityHandler.post(new Runnable() {
// @Override
// public void run() {
// new RenderersDialog().show(getSupportFragmentManager(), "renderers");
// }
// });
}
@Override
......
......@@ -31,7 +31,7 @@ import org.videolan.vlc.gui.video.VideoPlayerActivity;
public abstract class PlaybackServiceFragment extends Fragment implements PlaybackService.Client.Callback {
protected PlaybackService mService;
private static PlaybackServiceActivity.Helper getHelper(Activity activity) {
public static PlaybackServiceActivity.Helper getHelper(Activity activity) {
if (activity == null)
return null;
......
......@@ -36,6 +36,7 @@ import android.widget.Filter;
import android.widget.Filterable;
import android.widget.Toast;
import org.jetbrains.annotations.NotNull;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.medialibrary.media.MediaWrapper;
import org.videolan.vlc.PlaybackService;
......@@ -46,6 +47,7 @@ import org.videolan.vlc.gui.DiffUtilAdapter;
import org.videolan.vlc.gui.helpers.UiTools;
import org.videolan.vlc.interfaces.SwipeDragHelperAdapter;
import org.videolan.vlc.media.MediaUtils;
import org.videolan.vlc.util.MediaItemDiffCallback;
import org.videolan.vlc.util.WeakHandler;
import java.util.ArrayList;
......@@ -298,4 +300,10 @@ public class PlaylistAdapter extends DiffUtilAdapter<MediaWrapper, PlaylistAdapt
update((ArrayList<MediaWrapper>) filterResults.values);
}
}
@NotNull
@Override
protected DiffCallback<MediaWrapper> createCB() {
return new MediaItemDiffCallback();
}
}
/*****************************************************************************
* RenderersDialog.java
*
* Copyright © 2017 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*/
package org.videolan.vlc.gui.dialogs
import android.content.DialogInterface
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v4.content.ContextCompat
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import org.videolan.libvlc.RendererItem
import org.videolan.vlc.PlaybackService
import org.videolan.vlc.R
import org.videolan.vlc.RendererDelegate
import org.videolan.vlc.databinding.DialogRenderersBinding
import org.videolan.vlc.databinding.ItemRendererBinding
import org.videolan.vlc.gui.DiffUtilAdapter
import org.videolan.vlc.gui.PlaybackServiceFragment
import org.videolan.vlc.gui.helpers.SelectorViewHolder
class RenderersDialog : DialogFragment(), RendererDelegate.RendererListener, PlaybackService.Client.Callback {
companion object {