/* * This file is part of Popcorn Time. * * Popcorn Time 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 3 of the License, or * (at your option) any later version. * * Popcorn Time 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 Popcorn Time. If not, see <http://www.gnu.org/licenses/>. */ package pct.droid.base.beaming; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.text.InputType; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.Toast; import com.connectsdk.core.ImageInfo; import com.connectsdk.core.MediaInfo; import com.connectsdk.device.ConnectableDevice; import com.connectsdk.device.ConnectableDeviceListener; import com.connectsdk.discovery.CapabilityFilter; import com.connectsdk.discovery.DiscoveryManager; import com.connectsdk.discovery.DiscoveryManagerListener; import com.connectsdk.discovery.provider.CastDiscoveryProvider; import com.connectsdk.discovery.provider.FireTVDiscoveryProvider; import com.connectsdk.discovery.provider.SSDPDiscoveryProvider; import com.connectsdk.discovery.provider.ZeroconfDiscoveryProvider; import com.connectsdk.service.AirPlayService; import com.connectsdk.service.CastService; import com.connectsdk.service.DIALService; import com.connectsdk.service.DLNAService; import com.connectsdk.service.DeviceService; import com.connectsdk.service.FireTVService; import com.connectsdk.service.NetcastTVService; import com.connectsdk.service.RokuService; import com.connectsdk.service.WebOSTVService; import com.connectsdk.service.capability.MediaControl; import com.connectsdk.service.capability.MediaPlayer; import com.connectsdk.service.capability.VolumeControl; import com.connectsdk.service.command.ServiceCommandError; import com.connectsdk.service.sessions.LaunchSession; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; import pct.droid.base.PopcornApplication; import pct.droid.base.R; import pct.droid.base.beaming.server.BeamServer; import pct.droid.base.beaming.server.BeamServerService; import pct.droid.base.providers.subs.SubsProvider; import pct.droid.base.subs.FatalParsingException; import pct.droid.base.subs.FormatSRT; import pct.droid.base.subs.TimedTextObject; import pct.droid.base.torrent.StreamInfo; import pct.droid.base.utils.FileUtils; import timber.log.Timber; /** * CastingManager.java * <p/> * Wrapper for ConnectSDK */ public class BeamManager implements ConnectableDeviceListener, DiscoveryManagerListener { private static BeamManager sInstance; private Context mContext; private DiscoveryManager mDiscoveryManager; private ConnectableDevice mCurrentDevice; private LaunchSession mLaunchSession; private Boolean mConnected = false; private List<BeamListener> mListeners = new ArrayList<>(); private List<ConnectableDeviceListener> mDeviceListeners = new ArrayList<>(); private InputMethodManager mInputManager; private EditText mInput; private AlertDialog mPairingAlertDialog; private AlertDialog mPairingCodeDialog; private StreamInfo mStreamInfo; private BeamManager(Context context) { mContext = context; mInput = new EditText(context); mInput.setInputType(InputType.TYPE_CLASS_NUMBER); mInputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); mPairingAlertDialog = new AlertDialog.Builder(context) .setTitle(R.string.pairing_tv) .setMessage(R.string.confirm_tv) .setPositiveButton(android.R.string.ok, null) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .create(); mPairingCodeDialog = new AlertDialog.Builder(context) .setTitle(R.string.enter_pairing_code) .setView(mInput) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { if (mCurrentDevice != null) { String value = mInput.getText().toString().trim(); mCurrentDevice.sendPairingKey(value); mInputManager.hideSoftInputFromWindow(mInput.getWindowToken(), 0); } } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int whichButton) { dialog.dismiss(); mInputManager.hideSoftInputFromWindow(mInput.getWindowToken(), 0); } }) .create(); // CastService.setApplicationID(Constants.CAST_ID); Do not use since suspended by Google DiscoveryManager.init(PopcornApplication.getAppContext()); mDiscoveryManager = DiscoveryManager.getInstance(); mDiscoveryManager.registerDeviceService(CastService.class, CastDiscoveryProvider.class); mDiscoveryManager.registerDeviceService(RokuService.class, SSDPDiscoveryProvider.class); mDiscoveryManager.registerDeviceService(DLNAService.class, SSDPDiscoveryProvider.class); mDiscoveryManager.registerDeviceService(NetcastTVService.class, SSDPDiscoveryProvider.class); mDiscoveryManager.registerDeviceService(WebOSTVService.class, SSDPDiscoveryProvider.class); mDiscoveryManager.registerDeviceService(AirPlayService.class, ZeroconfDiscoveryProvider.class); mDiscoveryManager.registerDeviceService(FireTVService.class, FireTVDiscoveryProvider.class); mDiscoveryManager.unregisterDeviceService(DIALService.class, SSDPDiscoveryProvider.class); mDiscoveryManager.setPairingLevel(DiscoveryManager.PairingLevel.ON); mDiscoveryManager.setCapabilityFilters(new CapabilityFilter( MediaPlayer.Play_Video, MediaControl.Any )); mDiscoveryManager.start(); mDiscoveryManager.addListener(this); Intent castServerService = new Intent(context, BeamServerService.class); context.startService(castServerService); } public static BeamManager getInstance(Context context) { if (sInstance == null) { sInstance = new BeamManager(context); } return sInstance; } public void onDestroy() { mDiscoveryManager.removeListener(this); mDiscoveryManager.stop(); mDiscoveryManager.onDestroy(); Intent castServerService = new Intent(mContext, BeamServerService.class); mContext.stopService(castServerService); } public Map<String, ConnectableDevice> getDevices() { Map<String, ConnectableDevice> devices = mDiscoveryManager.getCompatibleDevices(); for (String key : devices.keySet()) { ConnectableDevice device = devices.get(key); if (device.getServices().size() <= 0) { devices.remove(key); } } return devices; } public ConnectableDevice getConnectedDevice() { return mCurrentDevice; } public boolean hasCastDevices() { return mDiscoveryManager.getCompatibleDevices().size() > 0; } public boolean isConnected() { return mConnected; } public MediaControl getMediaControl() { return mCurrentDevice.getCapability(MediaControl.class); } public VolumeControl getVolumeControl() { if (mCurrentDevice != null && mCurrentDevice.hasCapability(VolumeControl.Volume_Get) && mCurrentDevice.hasCapability(VolumeControl.Volume_Get) && mCurrentDevice.hasCapability(VolumeControl.Volume_Subscribe)) { return mCurrentDevice.getCapability(VolumeControl.class); } return null; } public boolean stopVideo() { if (!mConnected || mLaunchSession == null) return false; mLaunchSession.close(null); mLaunchSession = null; mStreamInfo = null; return true; } public void playVideo(StreamInfo info) { playVideo(info, null); } public void playVideo(StreamInfo info, final MediaPlayer.LaunchListener listener) { if (!mConnected) listener.onError(ServiceCommandError.getError(503)); mStreamInfo = info; String location = info.getVideoLocation(); if(!location.startsWith("http")) { BeamServer.setCurrentVideo(location); location = BeamServer.getVideoURL(); } String subsLocation = null; if(info.getSubtitleLanguage() != null && !info.getSubtitleLanguage().isEmpty() && !info.getSubtitleLanguage().equals("no-subs")) { File srtFile = new File(SubsProvider.getStorageLocation(mContext), mStreamInfo.getMedia().videoId + "-" + mStreamInfo.getSubtitleLanguage() + ".srt"); BeamServer.setCurrentSubs(srtFile); if(mCurrentDevice.hasCapability(MediaPlayer.Subtitles_Vtt)) { subsLocation = BeamServer.getSubsURL(BeamServer.VTT); } else if (mCurrentDevice.hasCapability(MediaPlayer.Subtitles_Srt)) { subsLocation = BeamServer.getSubsURL(BeamServer.SRT); } } else { BeamServer.removeSubs(); } try { URL url = new URL(location); URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef()); location = uri.toString(); if(subsLocation != null) { URL subsUrl = new URL(subsLocation); URI subsUri = new URI(subsUrl.getProtocol(), subsUrl.getUserInfo(), subsUrl.getHost(), subsUrl.getPort(), subsUrl.getPath(), subsUrl.getQuery(), subsUrl.getRef()); subsLocation = subsUri.toString(); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (URISyntaxException e) { e.printStackTrace(); } String title = info.getTitle(); String imageUrl = info.getImageUrl() == null ? "https://popcorntime.io/images/header-logo.png" : info.getImageUrl(); //String url, String mimeType, String title, String description, String iconSrc, boolean shouldLoop, LaunchListener listener if (mCurrentDevice != null) { MediaInfo mediaInfo = new MediaInfo(location, subsLocation, "video/mp4", title, ""); mediaInfo.addImages(new ImageInfo(imageUrl)); mCurrentDevice.getCapability(MediaPlayer.class).playMedia(mediaInfo, false, new MediaPlayer.LaunchListener() { @Override public void onSuccess(MediaPlayer.MediaLaunchObject object) { mLaunchSession = object.launchSession; if (listener != null) listener.onSuccess(object); } @Override public void onError(ServiceCommandError error) { Timber.e(error.getMessage()); if (listener != null) listener.onError(error); } }); } } public void connect(ConnectableDevice castingDevice) { if (castingDevice == mCurrentDevice) return; if (mCurrentDevice != null) { mCurrentDevice.removeListener(this); mCurrentDevice.disconnect(); mConnected = false; } mCurrentDevice = castingDevice; if (castingDevice != null) { mCurrentDevice.addListener(this); mCurrentDevice.connect(); } } public void disconnect() { if (mCurrentDevice != null) { mConnected = false; mCurrentDevice.disconnect(); mCurrentDevice.removeListener(this); } for (BeamListener listener : mListeners) listener.updateBeamIcon(); onDeviceDisconnected(mCurrentDevice); mCurrentDevice = null; } public void addDiscoveryListener(DiscoveryManagerListener listener) { mDiscoveryManager.addListener(listener); } public void removeDiscoveryListener(DiscoveryManagerListener listener) { mDiscoveryManager.removeListener(listener); } public void addListener(BeamListener listener) { mListeners.add(listener); } public void removeListener(BeamListener listener) { mListeners.remove(listener); } public void addDeviceListener(ConnectableDeviceListener listener) { mDeviceListeners.add(listener); } public void removeDeviceListener(ConnectableDeviceListener listener) { mDeviceListeners.remove(listener); } public StreamInfo getStreamInfo() { return mStreamInfo; } @Override public void onDeviceReady(ConnectableDevice device) { if (mPairingAlertDialog.isShowing()) { mPairingAlertDialog.dismiss(); } mConnected = true; for (BeamListener listener : mListeners) listener.updateBeamIcon(); for(ConnectableDeviceListener listener : mDeviceListeners) listener.onDeviceReady(device); } @Override public void onDeviceDisconnected(ConnectableDevice device) { mConnected = false; for (BeamListener listener : mListeners) listener.updateBeamIcon(); for(ConnectableDeviceListener listener : mDeviceListeners) listener.onDeviceDisconnected(device); } @Override public void onPairingRequired(ConnectableDevice device, DeviceService service, DeviceService.PairingType pairingType) { switch (pairingType) { case FIRST_SCREEN: mPairingAlertDialog.show(); break; case PIN_CODE: mPairingCodeDialog.show(); break; case NONE: default: break; } for(ConnectableDeviceListener listener : mDeviceListeners) listener.onPairingRequired(device, service, pairingType); } @Override public void onCapabilityUpdated(ConnectableDevice device, List<String> added, List<String> removed) { for(ConnectableDeviceListener listener : mDeviceListeners) listener.onCapabilityUpdated(device, added, removed); } @Override public void onConnectionFailed(ConnectableDevice device, ServiceCommandError error) { Toast.makeText(mContext, R.string.unknown_error, Toast.LENGTH_SHORT).show(); for(ConnectableDeviceListener listener : mDeviceListeners) listener.onConnectionFailed(device, error); } @Override public void onDeviceAdded(DiscoveryManager manager, ConnectableDevice device) { for (BeamListener listener : mListeners) listener.updateBeamIcon(); } @Override public void onDeviceUpdated(DiscoveryManager manager, ConnectableDevice device) { } @Override public void onDeviceRemoved(DiscoveryManager manager, ConnectableDevice device) { if (device == mCurrentDevice) { mConnected = false; mCurrentDevice = null; } for (BeamListener listener : mListeners) listener.updateBeamIcon(); } @Override public void onDiscoveryFailed(DiscoveryManager manager, ServiceCommandError error) { } public interface BeamListener { void updateBeamIcon(); } }