values = subscription.getCurrentValues();
134 | if (values != null && values.containsKey("LastChange")) {
135 | String lastChangeValue = values.get("LastChange").toString();
136 | Log.i(TAG, "LastChange:" + lastChangeValue);
137 | LastChange lastChange;
138 | try {
139 | lastChange = new LastChange(new AVTransportLastChangeParser(), lastChangeValue);
140 | } catch (Exception e) {
141 | e.printStackTrace();
142 | return;
143 | }
144 |
145 | //Parse TransportState value.
146 | AVTransportVariable.TransportState transportState = lastChange.getEventedValue(0, AVTransportVariable.TransportState.class);
147 | if (transportState != null) {
148 | TransportState ts = transportState.getValue();
149 | if (ts == TransportState.PLAYING) {
150 | Intent intent = new Intent(VIntents.ACTION_PLAYING);
151 | sendBroadcast(intent);
152 | } else if (ts == TransportState.PAUSED_PLAYBACK) {
153 | Intent intent = new Intent(VIntents.ACTION_PAUSED_PLAYBACK);
154 | sendBroadcast(intent);
155 | } else if (ts == TransportState.STOPPED) {
156 | Intent intent = new Intent(VIntents.ACTION_STOPPED);
157 | sendBroadcast(intent);
158 | }
159 | }
160 |
161 | //Parse CurrentTrackMetaData value.
162 | EventedValueString currentTrackMetaData = lastChange.getEventedValue(0, AVTransportVariable.CurrentTrackMetaData.class);
163 | if (currentTrackMetaData != null && currentTrackMetaData.getValue() != null) {
164 | DIDLParser didlParser = new DIDLParser();
165 | Intent lastChangeIntent;
166 | try {
167 | DIDLContent content = didlParser.parse(currentTrackMetaData.getValue());
168 | Item item = content.getItems().get(0);
169 | String creator = item.getCreator();
170 | String title = item.getTitle();
171 |
172 | lastChangeIntent = new Intent(VIntents.ACTION_UPDATE_LAST_CHANGE);
173 | lastChangeIntent.putExtra("creator", creator);
174 | lastChangeIntent.putExtra("title", title);
175 | } catch (Exception e) {
176 | Log.e(TAG, "Parse CurrentTrackMetaData error.");
177 | lastChangeIntent = null;
178 | }
179 |
180 | if (lastChangeIntent != null)
181 | sendBroadcast(lastChangeIntent);
182 | }
183 | }
184 | }
185 |
186 | @Override
187 | protected void eventsMissed(GENASubscription subscription, int numberOfMissedEvents) {
188 | }
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/service/upnp/AndroidJettyServletContainer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Kevin Shen
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.vmloft.develop.app.screencast.service.upnp;
17 |
18 | import org.eclipse.jetty.server.AbstractHttpConnection;
19 | import org.eclipse.jetty.server.Connector;
20 | import org.eclipse.jetty.server.Request;
21 | import org.eclipse.jetty.server.Server;
22 | import org.eclipse.jetty.server.bio.SocketConnector;
23 | import org.eclipse.jetty.servlet.ServletContextHandler;
24 | import org.eclipse.jetty.servlet.ServletHolder;
25 | import org.eclipse.jetty.util.thread.ExecutorThreadPool;
26 | import org.fourthline.cling.transport.spi.ServletContainerAdapter;
27 |
28 | import java.io.IOException;
29 | import java.net.Socket;
30 | import java.util.concurrent.ExecutorService;
31 | import java.util.logging.Level;
32 | import java.util.logging.Logger;
33 |
34 | import javax.servlet.Servlet;
35 | import javax.servlet.http.HttpServletRequest;
36 |
37 |
38 | public class AndroidJettyServletContainer implements ServletContainerAdapter {
39 | final private static Logger log = Logger.getLogger(AndroidJettyServletContainer.class.getName());
40 |
41 | // Singleton
42 | public static final AndroidJettyServletContainer INSTANCE = new AndroidJettyServletContainer();
43 |
44 | private AndroidJettyServletContainer() {
45 | resetServer();
46 | }
47 |
48 | protected Server server;
49 |
50 | @Override
51 | synchronized public void setExecutorService(ExecutorService executorService) {
52 | if (INSTANCE.server.getThreadPool() == null) {
53 | INSTANCE.server.setThreadPool(new ExecutorThreadPool(executorService) {
54 | @Override
55 | protected void doStop() throws Exception {
56 | // Do nothing, don't shut down the Cling ExecutorService when Jetty stops!
57 | }
58 | });
59 | }
60 | }
61 |
62 | @Override
63 | synchronized public int addConnector(String host, int port) throws IOException {
64 | SocketConnector connector = new SocketConnector();
65 | connector.setHost(host);
66 | connector.setPort(port);
67 |
68 | // Open immediately so we can get the assigned local port
69 | connector.open();
70 |
71 | // Only add if open() succeeded
72 | server.addConnector(connector);
73 |
74 | // stats the connector if the server is started (server starts all connectors when started)
75 | if (server.isStarted()) {
76 | try {
77 | connector.start();
78 | } catch (Exception ex) {
79 | log.severe("Couldn't start connector: " + connector + " " + ex);
80 | throw new RuntimeException(ex);
81 | }
82 | }
83 | return connector.getLocalPort();
84 | }
85 |
86 | synchronized public void removeConnector(String host, int port) {
87 | Connector[] connectors = server.getConnectors();
88 | if (connectors == null)
89 | return;
90 |
91 | for (Connector connector : connectors) {
92 | //Fix getPort()
93 | if (connector.getHost().equals(host) && connector.getLocalPort() == port) {
94 | if (connector.isStarted() || connector.isStarting()) {
95 | try {
96 | connector.stop();
97 | } catch (Exception ex) {
98 | log.severe("Couldn't stop connector: " + connector + " " + ex);
99 | throw new RuntimeException(ex);
100 | }
101 | }
102 | server.removeConnector(connector);
103 | if (connectors.length == 1) {
104 | log.info("No more connectors, stopping Jetty server");
105 | stopIfRunning();
106 | }
107 | break;
108 | }
109 | }
110 | }
111 |
112 | @Override
113 | synchronized public void registerServlet(String contextPath, Servlet servlet) {
114 | if (server.getHandler() != null) {
115 | return;
116 | }
117 | log.info("Registering UPnP servlet under context path: " + contextPath);
118 | ServletContextHandler servletHandler =
119 | new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
120 | if (contextPath != null && contextPath.length() > 0)
121 | servletHandler.setContextPath(contextPath);
122 | ServletHolder s = new ServletHolder(servlet);
123 | servletHandler.addServlet(s, "/*");
124 | server.setHandler(servletHandler);
125 | }
126 |
127 | @Override
128 | synchronized public void startIfNotRunning() {
129 | if (!server.isStarted() && !server.isStarting()) {
130 | log.info("Starting Jetty server... ");
131 | try {
132 | server.start();
133 | } catch (Exception ex) {
134 | log.severe("Couldn't start Jetty server: " + ex);
135 | throw new RuntimeException(ex);
136 | }
137 | }
138 | }
139 |
140 | @Override
141 | synchronized public void stopIfRunning() {
142 | if (!server.isStopped() && !server.isStopping()) {
143 | log.info("Stopping Jetty server...");
144 | try {
145 | server.stop();
146 | } catch (Exception ex) {
147 | log.severe("Couldn't stop Jetty server: " + ex);
148 | throw new RuntimeException(ex);
149 | } finally {
150 | resetServer();
151 | }
152 | }
153 | }
154 |
155 | protected void resetServer() {
156 | server = new Server(); // Has its own QueuedThreadPool
157 | server.setGracefulShutdown(1000); // Let's wait a second for ongoing transfers to complete
158 | }
159 |
160 | /**
161 | * Casts the request to a Jetty API and tries to write a space character to the output stream of the socket.
162 | *
163 | * This space character might confuse the HTTP client. The Cling transports for Jetty Client and
164 | * Apache HttpClient have been tested to work with space characters. Unfortunately, Sun JDK's
165 | * HttpURLConnection does not gracefully handle any garbage in the HTTP request!
166 | *
167 | */
168 | public static boolean isConnectionOpen(HttpServletRequest request) {
169 | return isConnectionOpen(request, " ".getBytes());
170 | }
171 |
172 | public static boolean isConnectionOpen(HttpServletRequest request, byte[] heartbeat) {
173 | Request jettyRequest = (Request) request;
174 | AbstractHttpConnection connection = jettyRequest.getConnection();
175 | Socket socket = (Socket) connection.getEndPoint().getTransport();
176 | if (log.isLoggable(Level.FINE))
177 | log.fine("Checking if client connection is still open: " + socket.getRemoteSocketAddress());
178 | try {
179 | socket.getOutputStream().write(heartbeat);
180 | socket.getOutputStream().flush();
181 | return true;
182 | } catch (IOException ex) {
183 | if (log.isLoggable(Level.FINE))
184 | log.fine("Client connection has been closed: " + socket.getRemoteSocketAddress());
185 | return false;
186 | }
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/service/upnp/AudioResourceServlet.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Kevin Shen
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.vmloft.develop.app.screencast.service.upnp;
17 |
18 | import android.content.ContentUris;
19 | import android.database.Cursor;
20 | import android.net.Uri;
21 | import android.provider.MediaStore;
22 | import android.util.Log;
23 |
24 | import com.vmloft.develop.app.screencast.VApplication;
25 | import com.vmloft.develop.library.tools.utils.VMFile;
26 |
27 | import org.eclipse.jetty.servlet.DefaultServlet;
28 | import org.eclipse.jetty.util.resource.FileResource;
29 | import org.eclipse.jetty.util.resource.Resource;
30 |
31 | import java.io.File;
32 |
33 | public class AudioResourceServlet extends DefaultServlet {
34 |
35 | @Override
36 | public Resource getResource(String pathInContext) {
37 | Resource resource = null;
38 |
39 | Log.i(AudioResourceServlet.class.getSimpleName(), "Path:" + pathInContext);
40 | try {
41 | String id = VMFile.parseResourceId(pathInContext);
42 | Log.i(AudioResourceServlet.class.getSimpleName(), "Id:" + id);
43 |
44 | Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, Long.parseLong(id));
45 | Cursor cursor = VApplication.getContext()
46 | .getContentResolver()
47 | .query(uri, null, null, null, null);
48 | cursor.moveToFirst();
49 | String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));
50 | File file = new File(path);
51 | if (file.exists()) {
52 | resource = FileResource.newResource(file);
53 | }
54 | } catch (Exception e) {
55 | e.printStackTrace();
56 | }
57 |
58 | return resource;
59 | }
60 |
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/service/upnp/ClingContentDirectoryService.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.service.upnp;
2 |
3 | import com.vmloft.develop.app.screencast.VApplication;
4 | import com.vmloft.develop.app.screencast.database.MediaContentDao;
5 | import com.vmloft.develop.app.screencast.entity.VItem;
6 |
7 | import org.fourthline.cling.support.contentdirectory.AbstractContentDirectoryService;
8 | import org.fourthline.cling.support.contentdirectory.ContentDirectoryErrorCode;
9 | import org.fourthline.cling.support.contentdirectory.ContentDirectoryException;
10 | import org.fourthline.cling.support.contentdirectory.DIDLParser;
11 | import org.fourthline.cling.support.model.BrowseFlag;
12 | import org.fourthline.cling.support.model.BrowseResult;
13 | import org.fourthline.cling.support.model.DIDLContent;
14 | import org.fourthline.cling.support.model.SortCriterion;
15 | import org.fourthline.cling.support.model.container.Container;
16 | import org.fourthline.cling.support.model.item.Item;
17 |
18 | import java.util.List;
19 |
20 | /**
21 | * Created by lzan13 on 2018/3/18.
22 | * 本地设备内容目录服务,主要用来读取本地音频和视频文件
23 | */
24 | public class ClingContentDirectoryService extends AbstractContentDirectoryService {
25 |
26 | @Override
27 | public BrowseResult browse(String objectID, BrowseFlag browseFlag, String filter, long firstResult, long maxResults, SortCriterion[] orderBy) throws ContentDirectoryException {
28 | Container resultBean = ContainerFactory.createContainer(objectID);
29 | DIDLContent content = new DIDLContent();
30 | for (Container c : resultBean.getContainers()) {
31 | content.addContainer(c);
32 | }
33 | for (Item item : resultBean.getItems()) {
34 | content.addItem(item);
35 | }
36 | int count = resultBean.getChildCount();
37 | String contentModel = "";
38 | try {
39 | contentModel = new DIDLParser().generate(content);
40 | } catch (Exception e) {
41 | throw new ContentDirectoryException(ContentDirectoryErrorCode.CANNOT_PROCESS, e.toString());
42 | }
43 | return new BrowseResult(contentModel, count, count);
44 | }
45 |
46 | static class ContainerFactory {
47 | static Container createContainer(String containerId) {
48 | Container result = new Container();
49 | result.setChildCount(0);
50 |
51 | if (VItem.ROOT_ID.equals(containerId)) {
52 | // 定义音频资源
53 | Container audioContainer = new Container();
54 | audioContainer.setId(VItem.AUDIO_ID);
55 | audioContainer.setParentID(VItem.ROOT_ID);
56 | audioContainer.setClazz(VItem.AUDIO_CLASS);
57 | audioContainer.setTitle("Audios");
58 |
59 | result.addContainer(audioContainer);
60 | result.setChildCount(result.getChildCount() + 1);
61 |
62 | // 定义图片资源
63 | Container imageContainer = new Container();
64 | imageContainer.setId(VItem.IMAGE_ID);
65 | imageContainer.setParentID(VItem.ROOT_ID);
66 | imageContainer.setClazz(VItem.IMAGE_CLASS);
67 | imageContainer.setTitle("Images");
68 |
69 | result.addContainer(imageContainer);
70 | result.setChildCount(result.getChildCount() + 1);
71 |
72 | // 定义视频资源
73 | Container videoContainer = new Container();
74 | videoContainer.setId(VItem.VIDEO_ID);
75 | videoContainer.setParentID(VItem.ROOT_ID);
76 | videoContainer.setClazz(VItem.VIDEO_CLASS);
77 | videoContainer.setTitle("Videos");
78 |
79 | result.addContainer(videoContainer);
80 | result.setChildCount(result.getChildCount() + 1);
81 | } else if (VItem.AUDIO_ID.equals(containerId)) {
82 | MediaContentDao contentDao = new MediaContentDao(VApplication.getContext());
83 | //Get audio items
84 | List- items = contentDao.getAudioItems();
85 | for (Item item : items) {
86 | result.addItem(item);
87 | result.setChildCount(result.getChildCount() + 1);
88 | }
89 | } else if (VItem.IMAGE_ID.equals(containerId)) {
90 | MediaContentDao contentDao = new MediaContentDao(VApplication.getContext());
91 | //Get image items
92 | List
- items = contentDao.getImageItems();
93 | for (Item item : items) {
94 | result.addItem(item);
95 | result.setChildCount(result.getChildCount() + 1);
96 | }
97 | } else if (VItem.VIDEO_ID.equals(containerId)) {
98 | MediaContentDao contentDao = new MediaContentDao(VApplication.getContext());
99 | //Get video items
100 | List
- items = contentDao.getVideoItems();
101 | for (Item item : items) {
102 | result.addItem(item);
103 | result.setChildCount(result.getChildCount() + 1);
104 | }
105 | }
106 | return result;
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/service/upnp/ImageResourceServlet.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Kevin Shen
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.vmloft.develop.app.screencast.service.upnp;
17 |
18 | import android.content.ContentUris;
19 | import android.database.Cursor;
20 | import android.net.Uri;
21 | import android.provider.MediaStore;
22 | import android.util.Log;
23 |
24 | import com.vmloft.develop.app.screencast.VApplication;
25 | import com.vmloft.develop.library.tools.utils.VMFile;
26 |
27 | import org.eclipse.jetty.servlet.DefaultServlet;
28 | import org.eclipse.jetty.util.resource.FileResource;
29 | import org.eclipse.jetty.util.resource.Resource;
30 |
31 | import java.io.File;
32 |
33 | public class ImageResourceServlet extends DefaultServlet {
34 |
35 | @Override
36 | public Resource getResource(String pathInContext) {
37 | Resource resource = null;
38 |
39 | Log.i(ImageResourceServlet.class.getSimpleName(), "Path:" + pathInContext);
40 | try {
41 | String id = VMFile.parseResourceId(pathInContext);
42 | Log.i(ImageResourceServlet.class.getSimpleName(), "Id:" + id);
43 |
44 | Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, Long.parseLong(id));
45 | Cursor cursor = VApplication.getContext()
46 | .getContentResolver()
47 | .query(uri, null, null, null, null);
48 | cursor.moveToFirst();
49 | String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
50 | File file = new File(path);
51 | if (file.exists()) {
52 | resource = FileResource.newResource(file);
53 | }
54 | } catch (Exception e) {
55 | e.printStackTrace();
56 | }
57 |
58 | return resource;
59 | }
60 |
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/service/upnp/JettyResourceServer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Kevin Shen
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.vmloft.develop.app.screencast.service.upnp;
17 |
18 | import com.vmloft.develop.app.screencast.VConstants;
19 |
20 | import org.eclipse.jetty.server.Server;
21 | import org.eclipse.jetty.servlet.ServletContextHandler;
22 |
23 | import java.util.logging.Logger;
24 |
25 | public class JettyResourceServer implements Runnable {
26 | final private static Logger log = Logger.getLogger(JettyResourceServer.class.getName());
27 |
28 | private Server mServer;
29 |
30 | public JettyResourceServer() {
31 | mServer = new Server(VConstants.JETTY_SERVER_PORT); // Has its own QueuedThreadPool
32 | mServer.setGracefulShutdown(1000); // Let's wait a second for ongoing transfers to complete
33 | }
34 |
35 | synchronized public void startIfNotRunning() {
36 | if (!mServer.isStarted() && !mServer.isStarting()) {
37 | log.info("Starting JettyResourceServer");
38 | try {
39 | mServer.start();
40 | } catch (Exception ex) {
41 | log.severe("Couldn't start Jetty server: " + ex);
42 | throw new RuntimeException(ex);
43 | }
44 | }
45 | }
46 |
47 | synchronized public void stopIfRunning() {
48 | if (!mServer.isStopped() && !mServer.isStopping()) {
49 | log.info("Stopping JettyResourceServer");
50 | try {
51 | mServer.stop();
52 | } catch (Exception ex) {
53 | log.severe("Couldn't stop Jetty server: " + ex);
54 | throw new RuntimeException(ex);
55 | }
56 | }
57 | }
58 |
59 | public String getServerState() {
60 | return mServer.getState();
61 | }
62 |
63 | @Override
64 | public void run() {
65 | ServletContextHandler context = new ServletContextHandler();
66 | context.setContextPath("/");
67 | context.setInitParameter("org.eclipse.jetty.servlet.Default.gzip", "false");
68 | mServer.setHandler(context);
69 |
70 | context.addServlet(AudioResourceServlet.class, "/audio/*");
71 | context.addServlet(ImageResourceServlet.class, "/image/*");
72 | context.addServlet(VideoResourceServlet.class, "/video/*");
73 |
74 | startIfNotRunning();
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/service/upnp/VideoResourceServlet.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Kevin Shen
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.vmloft.develop.app.screencast.service.upnp;
17 |
18 | import android.content.ContentUris;
19 | import android.database.Cursor;
20 | import android.net.Uri;
21 | import android.provider.MediaStore;
22 | import android.util.Log;
23 |
24 | import com.vmloft.develop.app.screencast.VApplication;
25 | import com.vmloft.develop.library.tools.utils.VMFile;
26 |
27 | import org.eclipse.jetty.servlet.DefaultServlet;
28 | import org.eclipse.jetty.util.resource.FileResource;
29 | import org.eclipse.jetty.util.resource.Resource;
30 |
31 | import java.io.File;
32 |
33 | public class VideoResourceServlet extends DefaultServlet {
34 |
35 | @Override
36 | public Resource getResource(String pathInContext) {
37 | Resource resource = null;
38 |
39 | Log.i(VideoResourceServlet.class.getSimpleName(), "Path:" + pathInContext);
40 | try {
41 | String id = VMFile.parseResourceId(pathInContext);
42 | Log.i(VideoResourceServlet.class.getSimpleName(), "Id:" + id);
43 |
44 | Uri uri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, Long.parseLong(id));
45 | Cursor cursor = VApplication.getContext()
46 | .getContentResolver()
47 | .query(uri, null, null, null, null);
48 | cursor.moveToFirst();
49 | String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
50 | File file = new File(path);
51 | if (file.exists()) {
52 | resource = FileResource.newResource(file);
53 | }
54 | } catch (Exception e) {
55 | e.printStackTrace();
56 | }
57 |
58 | return resource;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/DeviceListActivity.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.FragmentManager;
5 | import android.support.v4.app.FragmentTransaction;
6 | import android.support.v7.widget.Toolbar;
7 | import android.view.View;
8 |
9 | import com.vmloft.develop.app.screencast.R;
10 | import com.vmloft.develop.library.tools.VMActivity;
11 |
12 | import butterknife.BindView;
13 | import butterknife.ButterKnife;
14 |
15 | /**
16 | * Created by lzan13 on 2018/3/19.
17 | * 投屏设备列表
18 | */
19 | public class DeviceListActivity extends VMActivity {
20 |
21 | @BindView(R.id.widget_toolbar) Toolbar toolbar;
22 |
23 | private DeviceListFragment deviceListFragment;
24 |
25 | @Override
26 | protected void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | setContentView(R.layout.activity_device_list);
29 |
30 | ButterKnife.bind(activity);
31 |
32 | init();
33 | }
34 |
35 | private void init() {
36 |
37 | toolbar.setTitle("选择投屏设备");
38 | setSupportActionBar(toolbar);
39 | toolbar.setNavigationIcon(R.drawable.ic_arrow_back_24dp);
40 | toolbar.setNavigationOnClickListener(new View.OnClickListener() {
41 | @Override
42 | public void onClick(View v) {
43 | finish();
44 | }
45 | });
46 |
47 | deviceListFragment = new DeviceListFragment();
48 | FragmentManager fragmentManager = getSupportFragmentManager();
49 | FragmentTransaction fts = fragmentManager.beginTransaction();
50 | fts.replace(R.id.fragment_container, deviceListFragment);
51 | fts.commit();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/DeviceListFragment.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui;
2 |
3 |
4 | import android.support.v7.widget.LinearLayoutManager;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.support.v7.widget.RecyclerView.LayoutManager;
7 | import android.widget.Toast;
8 |
9 | import com.vmloft.develop.app.screencast.R;
10 | import com.vmloft.develop.app.screencast.listener.ItemClickListener;
11 | import com.vmloft.develop.app.screencast.entity.ClingDevice;
12 | import com.vmloft.develop.app.screencast.manager.DeviceManager;
13 | import com.vmloft.develop.app.screencast.ui.adapter.ClingDeviceAdapter;
14 | import com.vmloft.develop.app.screencast.ui.event.DeviceEvent;
15 | import com.vmloft.develop.library.tools.VMFragment;
16 |
17 | import org.greenrobot.eventbus.EventBus;
18 | import org.greenrobot.eventbus.Subscribe;
19 | import org.greenrobot.eventbus.ThreadMode;
20 |
21 | import butterknife.BindView;
22 | import butterknife.ButterKnife;
23 |
24 | /**
25 | * Created by lzan13 on 2018/3/9.
26 | */
27 |
28 | public class DeviceListFragment extends VMFragment {
29 |
30 | @BindView(R.id.recycler_view) RecyclerView recycleView;
31 |
32 | private ClingDeviceAdapter adapter;
33 | private LayoutManager layoutManager;
34 |
35 | @Override
36 | protected int initLayoutId() {
37 | return R.layout.framgnet_device_list;
38 | }
39 |
40 | @Override
41 | protected void initView() {
42 | ButterKnife.bind(this, getView());
43 |
44 | layoutManager = new LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false);
45 | adapter = new ClingDeviceAdapter(activity);
46 | recycleView.setLayoutManager(layoutManager);
47 | recycleView.setAdapter(adapter);
48 |
49 | setItemClickListener();
50 | }
51 |
52 | @Override
53 | protected void initData() {
54 |
55 | }
56 |
57 | private void setItemClickListener() {
58 | adapter.setItemClickListener(new ItemClickListener() {
59 | @Override
60 | public void onItemAction(int action, Object object) {
61 | ClingDevice device = (ClingDevice) object;
62 | DeviceManager.getInstance().setCurrClingDevice(device);
63 | Toast.makeText(activity,
64 | "选择了设备 " + device.getDevice().getDetails().getFriendlyName(),
65 | Toast.LENGTH_LONG).show();
66 | refresh();
67 | }
68 | });
69 | }
70 |
71 | public void refresh() {
72 | if (adapter == null) {
73 | adapter = new ClingDeviceAdapter(activity);
74 | recycleView.setAdapter(adapter);
75 | }
76 | adapter.refresh();
77 | }
78 |
79 | @Subscribe(threadMode = ThreadMode.MAIN)
80 | public void onEventBus(DeviceEvent event) {
81 | refresh();
82 | }
83 |
84 | @Override
85 | public void onStart() {
86 | super.onStart();
87 | EventBus.getDefault().register(this);
88 | }
89 |
90 | @Override
91 | public void onStop() {
92 | super.onStop();
93 | EventBus.getDefault().unregister(this);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/LocalContentFragment.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui;
2 |
3 |
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v7.widget.LinearLayoutManager;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.support.v7.widget.RecyclerView.LayoutManager;
9 | import android.widget.LinearLayout;
10 |
11 | import com.vmloft.develop.app.screencast.R;
12 | import com.vmloft.develop.app.screencast.listener.ItemClickListener;
13 | import com.vmloft.develop.app.screencast.manager.ClingManager;
14 | import com.vmloft.develop.app.screencast.ui.adapter.LocalContentAdapter;
15 | import com.vmloft.develop.app.screencast.ui.event.DIDLEvent;
16 | import com.vmloft.develop.library.tools.VMFragment;
17 |
18 | import org.fourthline.cling.support.model.DIDLObject;
19 | import org.fourthline.cling.support.model.container.Container;
20 | import org.fourthline.cling.support.model.item.Item;
21 | import org.greenrobot.eventbus.EventBus;
22 | import org.greenrobot.eventbus.Subscribe;
23 | import org.greenrobot.eventbus.ThreadMode;
24 |
25 | import java.util.ArrayList;
26 | import java.util.List;
27 |
28 | import butterknife.BindView;
29 | import butterknife.ButterKnife;
30 | import butterknife.OnClick;
31 |
32 | /**
33 | * Created by lzan13 on 2018/3/19.
34 | * 本地内容界面
35 | */
36 | public class LocalContentFragment extends VMFragment {
37 |
38 | @BindView(R.id.layout_parent_directory) LinearLayout parentDirectory;
39 | @BindView(R.id.recycler_view) RecyclerView recyclerView;
40 |
41 | private LayoutManager layoutManager;
42 | private LocalContentAdapter adapter;
43 | private List objectList = new ArrayList<>();
44 |
45 | /**
46 | * 创建实例对象的工厂方法
47 | */
48 | public static LocalContentFragment newInstance() {
49 | LocalContentFragment fragment = new LocalContentFragment();
50 | Bundle args = new Bundle();
51 | fragment.setArguments(args);
52 | return fragment;
53 | }
54 |
55 |
56 | @Override
57 | protected int initLayoutId() {
58 | return R.layout.fragment_local_content;
59 | }
60 |
61 | @Override
62 | protected void initView() {
63 | ButterKnife.bind(this, getView());
64 | layoutManager = new LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false);
65 | adapter = new LocalContentAdapter(activity, objectList);
66 | recyclerView.setLayoutManager(layoutManager);
67 | recyclerView.setAdapter(adapter);
68 | setItemClickListener();
69 | }
70 |
71 | private void setItemClickListener() {
72 | adapter.setItemClickListener(new ItemClickListener() {
73 | @Override
74 | public void onItemAction(int action, Object object) {
75 | if (object instanceof Container) {
76 | Container container = (Container) object;
77 | searchLocalContent(container.getId());
78 | } else if (object instanceof Item) {
79 | Item item = (Item) object;
80 | ClingManager.getInstance().setLocalItem(item);
81 | startActivity(new Intent(activity, MediaPlayActivity.class));
82 | }
83 | }
84 | });
85 | }
86 |
87 | @Override
88 | protected void initData() {
89 | }
90 |
91 | @OnClick(R.id.layout_parent_directory)
92 | public void onClick() {
93 | searchLocalContent("0");
94 | }
95 |
96 |
97 | private void searchLocalContent(String containerId) {
98 | ClingManager.getInstance().searchLocalContent(containerId);
99 | }
100 |
101 | @Subscribe(threadMode = ThreadMode.MAIN)
102 | public void onEventBus(DIDLEvent event) {
103 | objectList.clear();
104 | if (event.content.getContainers().size() > 0) {
105 | objectList.addAll(event.content.getContainers());
106 | } else if (event.content.getItems().size() > 0) {
107 | objectList.addAll(event.content.getItems());
108 | }
109 | adapter.refresh();
110 | }
111 |
112 | @Override
113 | public void onStart() {
114 | super.onStart();
115 | EventBus.getDefault().register(this);
116 | }
117 |
118 | @Override
119 | public void onStop() {
120 | super.onStop();
121 | EventBus.getDefault().unregister(this);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.design.widget.TabLayout;
6 | import android.support.v4.app.Fragment;
7 | import android.support.v4.app.FragmentManager;
8 | import android.support.v4.app.FragmentPagerAdapter;
9 | import android.support.v4.view.ViewPager;
10 | import android.support.v7.widget.Toolbar;
11 |
12 | import com.vmloft.develop.app.screencast.R;
13 | import com.vmloft.develop.library.tools.VMActivity;
14 |
15 | import butterknife.BindView;
16 | import butterknife.ButterKnife;
17 | import butterknife.OnClick;
18 |
19 | public class MainActivity extends VMActivity {
20 |
21 | @BindView(R.id.widget_toolbar) Toolbar toolbar;
22 | @BindView(R.id.widget_tab_layout) TabLayout tabLayout;
23 | @BindView(R.id.view_pager) ViewPager viewPager;
24 |
25 | private LocalContentFragment localContentFragment;
26 | private RemoteContentFragment remoteContentFragment;
27 |
28 | private Fragment[] fragments;
29 | private int currTabIndex = 0;
30 | private String[] tabsTitle = new String[]{"本地资源", "远程资源"};
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_main);
36 |
37 | ButterKnife.bind(activity);
38 |
39 | init();
40 | initFragment();
41 | }
42 |
43 | private void init() {
44 | setSupportActionBar(toolbar);
45 | toolbar.setTitle(R.string.app_name);
46 | }
47 |
48 | private void initFragment() {
49 | localContentFragment = LocalContentFragment.newInstance();
50 | remoteContentFragment = RemoteContentFragment.newInstance();
51 |
52 | fragments = new Fragment[]{localContentFragment, remoteContentFragment};
53 | ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
54 | viewPager.setAdapter(adapter);
55 |
56 | // 设置 ViewPager 缓存个数
57 | viewPager.setOffscreenPageLimit(1);
58 | viewPager.setCurrentItem(currTabIndex);
59 | // 添加 ViewPager 页面改变监听
60 | viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
61 | @Override
62 | public void onPageScrolled(int position, float positionOffset,
63 | int positionOffsetPixels) {
64 | }
65 |
66 | @Override
67 | public void onPageSelected(int position) {
68 | }
69 |
70 | @Override
71 | public void onPageScrollStateChanged(int state) {
72 |
73 | }
74 | });
75 | tabLayout.setupWithViewPager(viewPager);
76 | }
77 |
78 | @OnClick(R.id.fab_screen_cast)
79 | public void onClick() {
80 | startActivity(new Intent(activity, DeviceListActivity.class));
81 | }
82 |
83 | @Override
84 | protected void onDestroy() {
85 | super.onDestroy();
86 |
87 | // ClingManager.getInstance().destroy();
88 | // ControlManager.getInstance().destroy();
89 | // DeviceManager.getInstance().destroy();
90 | }
91 |
92 |
93 | /**
94 | * 自定义 ViewPager 适配器子类
95 | */
96 | class ViewPagerAdapter extends FragmentPagerAdapter {
97 |
98 | public ViewPagerAdapter(FragmentManager fm) {
99 | super(fm);
100 | }
101 |
102 | @Override
103 | public CharSequence getPageTitle(int position) {
104 | return tabsTitle[position];
105 | }
106 |
107 | @Override
108 | public Fragment getItem(int position) {
109 | return fragments[position];
110 | }
111 |
112 | @Override
113 | public int getCount() {
114 | return tabsTitle.length;
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/MediaPlayActivity.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.widget.Toolbar;
6 | import android.text.TextUtils;
7 | import android.view.Menu;
8 | import android.view.MenuItem;
9 | import android.view.View;
10 | import android.widget.ImageView;
11 | import android.widget.SeekBar;
12 | import android.widget.TextView;
13 | import android.widget.Toast;
14 |
15 | import com.vmloft.develop.app.screencast.R;
16 | import com.vmloft.develop.app.screencast.callback.ControlCallback;
17 | import com.vmloft.develop.app.screencast.entity.AVTransportInfo;
18 | import com.vmloft.develop.app.screencast.entity.RemoteItem;
19 | import com.vmloft.develop.app.screencast.entity.RenderingControlInfo;
20 | import com.vmloft.develop.app.screencast.manager.ClingManager;
21 | import com.vmloft.develop.app.screencast.manager.ControlManager;
22 | import com.vmloft.develop.app.screencast.ui.event.ControlEvent;
23 | import com.vmloft.develop.library.tools.VMActivity;
24 | import com.vmloft.develop.library.tools.utils.VMDate;
25 | import com.vmloft.develop.library.tools.utils.VMLog;
26 |
27 | import org.fourthline.cling.support.model.item.Item;
28 | import org.greenrobot.eventbus.EventBus;
29 | import org.greenrobot.eventbus.Subscribe;
30 | import org.greenrobot.eventbus.ThreadMode;
31 |
32 | import butterknife.BindView;
33 | import butterknife.ButterKnife;
34 | import butterknife.OnClick;
35 |
36 | /**
37 | * Created by lzan13 on 2018/3/19.
38 | * 播放控制界面
39 | */
40 | public class MediaPlayActivity extends VMActivity {
41 | @BindView(R.id.widget_toolbar) Toolbar toolbar;
42 |
43 | @BindView(R.id.text_content_title) TextView contentTitleView;
44 | @BindView(R.id.text_content_url) TextView contentUrlView;
45 |
46 | @BindView(R.id.img_volume) ImageView volumeView;
47 | @BindView(R.id.seek_bar_volume) SeekBar volumeSeekbar;
48 | @BindView(R.id.seek_bar_progress) SeekBar progressSeekbar;
49 | @BindView(R.id.text_play_time) TextView playTimeView;
50 | @BindView(R.id.text_play_max_time) TextView playMaxTimeView;
51 | @BindView(R.id.img_stop) ImageView stopView;
52 | @BindView(R.id.img_previous) ImageView previousView;
53 | @BindView(R.id.img_play) ImageView playView;
54 | @BindView(R.id.img_next) ImageView nextView;
55 |
56 | public Item localItem;
57 | public RemoteItem remoteItem;
58 |
59 | private int defaultVolume = 10;
60 | private int currVolume = defaultVolume;
61 | private boolean isMute = false;
62 | private int currProgress = 0;
63 |
64 |
65 | @Override
66 | protected void onCreate(Bundle savedInstanceState) {
67 | super.onCreate(savedInstanceState);
68 | setContentView(R.layout.activity_media_play);
69 |
70 | ButterKnife.bind(activity);
71 |
72 | init();
73 | }
74 |
75 | private void init() {
76 | String title = getString(R.string.title_cast_control);
77 | toolbar.setTitle(title);
78 | setSupportActionBar(toolbar);
79 | toolbar.setNavigationIcon(R.drawable.ic_arrow_back_24dp);
80 | toolbar.setNavigationOnClickListener(new View.OnClickListener() {
81 | @Override
82 | public void onClick(View v) {
83 | finish();
84 | }
85 | });
86 |
87 | localItem = ClingManager.getInstance().getLocalItem();
88 | remoteItem = ClingManager.getInstance().getRemoteItem();
89 | String url = "";
90 | String duration = "";
91 | if (localItem != null) {
92 | title = localItem.getTitle();
93 | url = localItem.getFirstResource().getValue();
94 | duration = localItem.getFirstResource().getDuration();
95 | }
96 | if (remoteItem != null) {
97 | title = remoteItem.getTitle();
98 | url = remoteItem.getUrl();
99 | duration = remoteItem.getDuration();
100 | }
101 | toolbar.setTitle(title);
102 | contentTitleView.setText(title);
103 | contentUrlView.setText(url);
104 |
105 | if (!TextUtils.isEmpty(duration)) {
106 | playMaxTimeView.setText(duration);
107 | progressSeekbar.setMax((int) VMDate.fromTimeString(duration));
108 | }
109 |
110 | setVolumeSeekListener();
111 | setProgressSeekListener();
112 | }
113 |
114 | /**
115 | * 设置音量拖动监听
116 | */
117 | private void setVolumeSeekListener() {
118 | volumeSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
119 | @Override
120 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
121 | VMLog.d("Volume seek position: %d", progress);
122 | }
123 |
124 | @Override
125 | public void onStartTrackingTouch(SeekBar seekBar) {
126 |
127 | }
128 |
129 | @Override
130 | public void onStopTrackingTouch(SeekBar seekBar) {
131 | setVolume(seekBar.getProgress());
132 | }
133 | });
134 | }
135 |
136 | /**
137 | * 设置播放进度拖动监听
138 | */
139 | private void setProgressSeekListener() {
140 | progressSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
141 | @Override
142 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
143 | }
144 |
145 | @Override
146 | public void onStartTrackingTouch(SeekBar seekBar) {
147 |
148 | }
149 |
150 | @Override
151 | public void onStopTrackingTouch(SeekBar seekBar) {
152 | currProgress = seekBar.getProgress();
153 | playTimeView.setText(VMDate.toTimeString(currProgress));
154 | seekCast(currProgress);
155 | }
156 | });
157 | }
158 |
159 | @OnClick({R.id.img_volume, R.id.img_stop, R.id.img_previous, R.id.img_play, R.id.img_next})
160 | public void onClick(View view) {
161 | switch (view.getId()) {
162 | case R.id.img_volume:
163 | mute();
164 | break;
165 | case R.id.img_stop:
166 | stop();
167 | break;
168 | case R.id.img_previous:
169 | break;
170 | case R.id.img_play:
171 | play();
172 | break;
173 | case R.id.img_next:
174 |
175 | break;
176 | }
177 | }
178 |
179 | /**
180 | * 静音开关
181 | */
182 | private void mute() {
183 | // 先获取当前是否静音
184 | isMute = ControlManager.getInstance().isMute();
185 | ControlManager.getInstance().muteCast(!isMute, new ControlCallback() {
186 | @Override
187 | public void onSuccess() {
188 | ControlManager.getInstance().setMute(!isMute);
189 | if (isMute) {
190 | if (currVolume == 0) {
191 | currVolume = defaultVolume;
192 | }
193 | setVolume(currVolume);
194 | }
195 | // 这里是根据之前的状态判断的
196 | if (isMute) {
197 | volumeView.setImageResource(R.drawable.ic_volume_up_24dp);
198 | } else {
199 | volumeView.setImageResource(R.drawable.ic_volume_off_24dp);
200 | }
201 | }
202 |
203 | @Override
204 | public void onError(int code, String msg) {
205 | showToast(String.format("Mute cast failed %s", msg));
206 | }
207 | });
208 | }
209 |
210 | /**
211 | * 设置音量大小
212 | */
213 | private void setVolume(int volume) {
214 | currVolume = volume;
215 | ControlManager.getInstance().setVolumeCast(volume, new ControlCallback() {
216 | @Override
217 | public void onSuccess() {
218 |
219 | }
220 |
221 | @Override
222 | public void onError(int code, String msg) {
223 | showToast(String.format("Set cast volume failed %s", msg));
224 | }
225 | });
226 | }
227 |
228 |
229 | /**
230 | * 播放开关
231 | */
232 | private void play() {
233 | if (ControlManager.getInstance().getState() == ControlManager.CastState.STOPED) {
234 | if (localItem != null) {
235 | newPlayCastLocalContent();
236 | } else {
237 | newPlayCastRemoteContent();
238 | }
239 | } else if (ControlManager.getInstance().getState() == ControlManager.CastState.PAUSED) {
240 | playCast();
241 | } else if (ControlManager.getInstance().getState() == ControlManager.CastState.PLAYING) {
242 | pauseCast();
243 | } else {
244 | Toast.makeText(activity, "正在连接设备,稍后操作", Toast.LENGTH_SHORT).show();
245 | }
246 | }
247 |
248 | private void stop() {
249 | ControlManager.getInstance().unInitScreenCastCallback();
250 | stopCast();
251 | }
252 |
253 | private void newPlayCastLocalContent() {
254 | ControlManager.getInstance().setState(ControlManager.CastState.TRANSITIONING);
255 | ControlManager.getInstance().newPlayCast(localItem, new ControlCallback() {
256 | @Override
257 | public void onSuccess() {
258 | ControlManager.getInstance().setState(ControlManager.CastState.PLAYING);
259 | ControlManager.getInstance().initScreenCastCallback();
260 | runOnUiThread(new Runnable() {
261 | @Override
262 | public void run() {
263 | playView.setImageResource(R.drawable.ic_pause_circle_outline_24dp);
264 | }
265 | });
266 | }
267 |
268 | @Override
269 | public void onError(int code, String msg) {
270 | ControlManager.getInstance().setState(ControlManager.CastState.STOPED);
271 | showToast(String.format("New play cast local content failed %s", msg));
272 | }
273 | });
274 | }
275 |
276 | private void newPlayCastRemoteContent() {
277 | ControlManager.getInstance().setState(ControlManager.CastState.TRANSITIONING);
278 | ControlManager.getInstance().newPlayCast(remoteItem, new ControlCallback() {
279 | @Override
280 | public void onSuccess() {
281 | ControlManager.getInstance().setState(ControlManager.CastState.PLAYING);
282 | ControlManager.getInstance().initScreenCastCallback();
283 | runOnUiThread(new Runnable() {
284 | @Override
285 | public void run() {
286 | playView.setImageResource(R.drawable.ic_pause_circle_outline_24dp);
287 | }
288 | });
289 | }
290 |
291 | @Override
292 | public void onError(int code, String msg) {
293 | ControlManager.getInstance().setState(ControlManager.CastState.STOPED);
294 | showToast(String.format("New play cast remote content failed %s", msg));
295 | }
296 | });
297 | }
298 |
299 | private void playCast() {
300 | ControlManager.getInstance().playCast(new ControlCallback() {
301 | @Override
302 | public void onSuccess() {
303 | ControlManager.getInstance().setState(ControlManager.CastState.PLAYING);
304 | runOnUiThread(new Runnable() {
305 | @Override
306 | public void run() {
307 | playView.setImageResource(R.drawable.ic_pause_circle_outline_24dp);
308 | }
309 | });
310 | }
311 |
312 | @Override
313 | public void onError(int code, String msg) {
314 | showToast(String.format("Play cast failed %s", msg));
315 | }
316 | });
317 | }
318 |
319 | private void pauseCast() {
320 | ControlManager.getInstance().pauseCast(new ControlCallback() {
321 | @Override
322 | public void onSuccess() {
323 | ControlManager.getInstance().setState(ControlManager.CastState.PAUSED);
324 | runOnUiThread(new Runnable() {
325 | @Override
326 | public void run() {
327 | playView.setImageResource(R.drawable.ic_play_circle_outline_24dp);
328 | }
329 | });
330 | }
331 |
332 | @Override
333 | public void onError(int code, String msg) {
334 | showToast(String.format("Pause cast failed %s", msg));
335 | }
336 | });
337 | }
338 |
339 | private void stopCast() {
340 | ControlManager.getInstance().stopCast(new ControlCallback() {
341 | @Override
342 | public void onSuccess() {
343 | ControlManager.getInstance().setState(ControlManager.CastState.STOPED);
344 | runOnUiThread(new Runnable() {
345 | @Override
346 | public void run() {
347 | playView.setImageResource(R.drawable.ic_play_circle_outline_24dp);
348 | onFinish();
349 | }
350 | });
351 | }
352 |
353 | @Override
354 | public void onError(int code, String msg) {
355 | showToast(String.format("Stop cast failed %s", msg));
356 | }
357 | });
358 | }
359 |
360 | /**
361 | * 改变投屏进度
362 | */
363 | private void seekCast(int progress) {
364 | String target = VMDate.toTimeString(progress);
365 | ControlManager.getInstance().seekCast(target, new ControlCallback() {
366 | @Override
367 | public void onSuccess() {
368 |
369 | }
370 |
371 | @Override
372 | public void onError(int code, String msg) {
373 | showToast(String.format("Seek cast failed %s", msg));
374 | }
375 | });
376 | }
377 |
378 | @Subscribe(threadMode = ThreadMode.MAIN)
379 | public void onEventBus(ControlEvent event) {
380 | AVTransportInfo avtInfo = event.getAvtInfo();
381 | if (avtInfo != null) {
382 | if (!TextUtils.isEmpty(avtInfo.getState())) {
383 | if (avtInfo.getState().equals("TRANSITIONING")) {
384 | ControlManager.getInstance().setState(ControlManager.CastState.TRANSITIONING);
385 | } else if (avtInfo.getState().equals("PLAYING")) {
386 | ControlManager.getInstance().setState(ControlManager.CastState.PLAYING);
387 | playView.setImageResource(R.drawable.ic_pause_circle_outline_24dp);
388 | } else if (avtInfo.getState().equals("PAUSED_PLAYBACK")) {
389 | ControlManager.getInstance().setState(ControlManager.CastState.PAUSED);
390 | playView.setImageResource(R.drawable.ic_play_circle_outline_24dp);
391 | } else if (avtInfo.getState().equals("STOPPED")) {
392 | ControlManager.getInstance().setState(ControlManager.CastState.STOPED);
393 | playView.setImageResource(R.drawable.ic_play_circle_outline_24dp);
394 | onFinish();
395 | } else {
396 | ControlManager.getInstance().setState(ControlManager.CastState.STOPED);
397 | playView.setImageResource(R.drawable.ic_play_circle_outline_24dp);
398 | onFinish();
399 | }
400 | }
401 | if (!TextUtils.isEmpty(avtInfo.getMediaDuration())) {
402 | playMaxTimeView.setText(avtInfo.getMediaDuration());
403 | }
404 | if (!TextUtils.isEmpty(avtInfo.getTimePosition())) {
405 | long progress = VMDate.fromTimeString(avtInfo.getTimePosition());
406 | progressSeekbar.setProgress((int) progress);
407 | playTimeView.setText(avtInfo.getTimePosition());
408 | }
409 | }
410 |
411 | RenderingControlInfo rcInfo = event.getRcInfo();
412 | if (rcInfo != null && ControlManager.getInstance()
413 | .getState() != ControlManager.CastState.STOPED) {
414 | if (rcInfo.isMute() || rcInfo.getVolume() == 0) {
415 | volumeView.setImageResource(R.drawable.ic_volume_off_24dp);
416 | ControlManager.getInstance().setMute(true);
417 | } else {
418 | volumeView.setImageResource(R.drawable.ic_volume_up_24dp);
419 | ControlManager.getInstance().setMute(false);
420 | }
421 | volumeSeekbar.setProgress(rcInfo.getVolume());
422 | }
423 | }
424 |
425 | private void showToast(final String msg) {
426 | runOnUiThread(new Runnable() {
427 | @Override
428 | public void run() {
429 | Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show();
430 | }
431 | });
432 | }
433 |
434 | @Override
435 | public boolean onCreateOptionsMenu(Menu menu) {
436 | getMenuInflater().inflate(R.menu.play_menu, menu);
437 | return true;
438 | }
439 |
440 | @Override
441 | public boolean onOptionsItemSelected(MenuItem item) {
442 | switch (item.getItemId()) {
443 | case R.id.screen_cast:
444 | startActivity(new Intent(activity, DeviceListActivity.class));
445 | break;
446 | default:
447 | return super.onOptionsItemSelected(item);
448 | }
449 | return true;
450 | }
451 |
452 |
453 | @Override
454 | public void onStart() {
455 | super.onStart();
456 | EventBus.getDefault().register(this);
457 | }
458 |
459 | @Override
460 | public void onStop() {
461 | super.onStop();
462 | EventBus.getDefault().unregister(this);
463 | }
464 | }
465 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/RemoteContentFragment.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui;
2 |
3 |
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v7.widget.LinearLayoutManager;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.support.v7.widget.RecyclerView.LayoutManager;
9 |
10 | import com.vmloft.develop.app.screencast.R;
11 | import com.vmloft.develop.app.screencast.listener.ItemClickListener;
12 | import com.vmloft.develop.app.screencast.entity.RemoteItem;
13 | import com.vmloft.develop.app.screencast.manager.ClingManager;
14 | import com.vmloft.develop.app.screencast.ui.adapter.RemoteContentAdapter;
15 | import com.vmloft.develop.library.tools.VMFragment;
16 |
17 | import java.util.ArrayList;
18 | import java.util.List;
19 |
20 | import butterknife.BindView;
21 | import butterknife.ButterKnife;
22 |
23 | /**
24 | * Created by lzan13 on 2018/3/19.
25 | * 远程内容界面
26 | */
27 | public class RemoteContentFragment extends VMFragment {
28 | @BindView(R.id.recycler_view) RecyclerView recyclerView;
29 |
30 | private LayoutManager layoutManager;
31 | private RemoteContentAdapter adapter;
32 |
33 | private List contentList = new ArrayList<>();
34 |
35 | /**
36 | * 创建实例对象的工厂方法
37 | *
38 | * @return 返回一个新的实例
39 | */
40 | public static RemoteContentFragment newInstance() {
41 | RemoteContentFragment fragment = new RemoteContentFragment();
42 | Bundle args = new Bundle();
43 | fragment.setArguments(args);
44 | return fragment;
45 | }
46 |
47 |
48 | @Override
49 | protected int initLayoutId() {
50 | return R.layout.fragment_remote_content;
51 | }
52 |
53 | @Override
54 | protected void initView() {
55 | ButterKnife.bind(this, getView());
56 | layoutManager = new LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false);
57 | adapter = new RemoteContentAdapter(activity, contentList);
58 | recyclerView.setLayoutManager(layoutManager);
59 | recyclerView.setAdapter(adapter);
60 | setItemClickListener();
61 | }
62 |
63 | private void setItemClickListener() {
64 | adapter.setItemClickListener(new ItemClickListener() {
65 | @Override
66 | public void onItemAction(int action, Object object) {
67 | ClingManager.getInstance().setRemoteItem((RemoteItem) object);
68 | startActivity(new Intent(activity, MediaPlayActivity.class));
69 | }
70 | });
71 | }
72 |
73 | @Override
74 | protected void initData() {
75 | RemoteItem pfzlItem = new RemoteItem("平凡之路 - 朴树", "p1302", "朴树", 25203597, "00:05:05",
76 | "640x352", "http://melove.net/data/videos/mv/PingFanZhiLu.mp4");
77 | RemoteItem sugarItem = new RemoteItem("Sugar - 魔力红", "1452282796", "魔力红", 24410338,
78 | "00:05:01", "640x352", "http://melove.net/data/videos/mv/Sugar.mp4");
79 | RemoteItem wakeItem = new RemoteItem("Wake - Y&F", "425703", "Hillsong Young And Free",
80 | 107362668, "00:04:33", "1280x720", "http://melove.net/data/videos/mv/Wake.mp4");
81 | contentList.add(pfzlItem);
82 | contentList.add(sugarItem);
83 | contentList.add(wakeItem);
84 |
85 | refresh();
86 | }
87 |
88 | public void refresh() {
89 | adapter.refresh();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/adapter/ClingDeviceAdapter.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui.adapter;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import android.widget.TextView;
11 |
12 | import com.vmloft.develop.app.screencast.R;
13 | import com.vmloft.develop.app.screencast.listener.ItemClickListener;
14 | import com.vmloft.develop.app.screencast.entity.ClingDevice;
15 | import com.vmloft.develop.app.screencast.manager.DeviceManager;
16 |
17 | import java.util.List;
18 |
19 | import butterknife.BindView;
20 | import butterknife.ButterKnife;
21 |
22 | /**
23 | * Created by lzan13 on 2018/3/9.
24 | * Cling 设备列表适配器
25 | */
26 | public class ClingDeviceAdapter extends RecyclerView.Adapter {
27 |
28 | private LayoutInflater layoutInflater;
29 | private List clingDevices;
30 | private ItemClickListener clickListener;
31 |
32 | public ClingDeviceAdapter(Context context) {
33 | super();
34 | layoutInflater = LayoutInflater.from(context);
35 | clingDevices = DeviceManager.getInstance().getClingDeviceList();
36 | }
37 |
38 | @NonNull
39 | @Override
40 | public ClingHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
41 | View view = layoutInflater.inflate(R.layout.item_common_layout, parent, false);
42 | return new ClingHolder(view);
43 | }
44 |
45 | @Override
46 | public void onBindViewHolder(@NonNull ClingHolder holder, final int position) {
47 | final ClingDevice device = clingDevices.get(position);
48 | if (device == DeviceManager.getInstance().getCurrClingDevice()) {
49 | holder.iconView.setVisibility(View.VISIBLE);
50 | } else {
51 | holder.iconView.setVisibility(View.INVISIBLE);
52 | }
53 | holder.nameView.setText(device.getDevice().getDetails().getFriendlyName());
54 | holder.itemView.setOnClickListener(new View.OnClickListener() {
55 | @Override
56 | public void onClick(View v) {
57 | if (clickListener != null) {
58 | clickListener.onItemAction(position, device);
59 | }
60 | }
61 | });
62 | }
63 |
64 | @Override
65 | public int getItemCount() {
66 | return clingDevices.size();
67 | }
68 |
69 | public void refresh() {
70 | notifyDataSetChanged();
71 | }
72 |
73 | public void setItemClickListener(ItemClickListener listener) {
74 | this.clickListener = listener;
75 | }
76 |
77 | static class ClingHolder extends RecyclerView.ViewHolder {
78 | @BindView(R.id.text_name) TextView nameView;
79 | @BindView(R.id.img_icon) ImageView iconView;
80 |
81 | public ClingHolder(View itemView) {
82 | super(itemView);
83 | ButterKnife.bind(this, itemView);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/adapter/LocalContentAdapter.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui.adapter;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import android.widget.TextView;
11 |
12 | import com.vmloft.develop.app.screencast.R;
13 | import com.vmloft.develop.app.screencast.listener.ItemClickListener;
14 |
15 | import org.fourthline.cling.support.model.DIDLObject;
16 | import org.fourthline.cling.support.model.container.Container;
17 | import org.fourthline.cling.support.model.item.Item;
18 |
19 | import java.util.List;
20 |
21 | import butterknife.BindView;
22 | import butterknife.ButterKnife;
23 |
24 | /**
25 | * Created by lzan13 on 2018/3/19.
26 | */
27 |
28 | public class LocalContentAdapter extends RecyclerView.Adapter {
29 |
30 | private LayoutInflater layoutInflater;
31 | private List objectList;
32 | private ItemClickListener clickListener;
33 |
34 | public LocalContentAdapter(Context context, List list) {
35 | super();
36 | objectList = list;
37 | layoutInflater = LayoutInflater.from(context);
38 | }
39 |
40 | @NonNull
41 | @Override
42 | public ContentHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
43 | View view = layoutInflater.inflate(R.layout.item_common_layout, parent, false);
44 | return new ContentHolder(view);
45 | }
46 |
47 |
48 | @Override
49 | public void onBindViewHolder(@NonNull ContentHolder holder, final int position) {
50 | final DIDLObject object = objectList.get(position);
51 |
52 | if (object instanceof Container) {
53 | Container container = (Container) object;
54 | holder.nameView.setText(container.getTitle());
55 | holder.iconView.setImageResource(R.drawable.ic_folder_24dp);
56 | } else if (object instanceof Item) {
57 | Item item = (Item) object;
58 | holder.nameView.setText(item.getTitle());
59 | holder.iconView.setImageResource(R.drawable.ic_file_24dp);
60 | }
61 | holder.iconView.setVisibility(View.VISIBLE);
62 | holder.iconView.setColorFilter(R.color.vm_gray_87);
63 | holder.itemView.setOnClickListener(new View.OnClickListener() {
64 | @Override
65 | public void onClick(View v) {
66 | if (clickListener != null) {
67 | clickListener.onItemAction(position, object);
68 | }
69 | }
70 | });
71 | }
72 |
73 | @Override
74 | public int getItemCount() {
75 | return objectList.size();
76 | }
77 |
78 | public void refresh() {
79 | notifyDataSetChanged();
80 | }
81 |
82 | public void setItemClickListener(ItemClickListener listener) {
83 | this.clickListener = listener;
84 | }
85 |
86 | static class ContentHolder extends RecyclerView.ViewHolder {
87 | @BindView(R.id.text_name) TextView nameView;
88 | @BindView(R.id.img_icon) ImageView iconView;
89 |
90 | public ContentHolder(View itemView) {
91 | super(itemView);
92 | ButterKnife.bind(this, itemView);
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/adapter/RemoteContentAdapter.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui.adapter;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import android.widget.TextView;
11 |
12 | import com.vmloft.develop.app.screencast.R;
13 | import com.vmloft.develop.app.screencast.listener.ItemClickListener;
14 | import com.vmloft.develop.app.screencast.entity.RemoteItem;
15 |
16 | import java.util.List;
17 |
18 | import butterknife.BindView;
19 | import butterknife.ButterKnife;
20 |
21 | /**
22 | * Created by lzan13 on 2018/3/9.
23 | * Cling 设备列表适配器
24 | */
25 | public class RemoteContentAdapter extends RecyclerView.Adapter {
26 |
27 | private LayoutInflater layoutInflater;
28 | private List contentList;
29 | private ItemClickListener clickListener;
30 |
31 | public RemoteContentAdapter(Context context, List list) {
32 | super();
33 | layoutInflater = LayoutInflater.from(context);
34 | contentList = list;
35 | }
36 |
37 | @NonNull
38 | @Override
39 | public ContentHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
40 | View view = layoutInflater.inflate(R.layout.item_common_layout, parent, false);
41 | return new ContentHolder(view);
42 | }
43 |
44 | @Override
45 | public void onBindViewHolder(@NonNull ContentHolder holder, final int position) {
46 | final RemoteItem item = contentList.get(position);
47 | holder.iconView.setVisibility(View.VISIBLE);
48 | holder.iconView.setColorFilter(R.color.vm_gray_87);
49 | holder.iconView.setImageResource(R.drawable.ic_file_24dp);
50 | holder.nameView.setText(item.getTitle());
51 | holder.itemView.setOnClickListener(new View.OnClickListener() {
52 | @Override
53 | public void onClick(View v) {
54 | if (clickListener != null) {
55 | clickListener.onItemAction(position, item);
56 | }
57 | }
58 | });
59 | }
60 |
61 | @Override
62 | public int getItemCount() {
63 | return contentList.size();
64 | }
65 |
66 | public void refresh() {
67 | notifyDataSetChanged();
68 | }
69 |
70 | public void setItemClickListener(ItemClickListener listener) {
71 | this.clickListener = listener;
72 | }
73 |
74 | static class ContentHolder extends RecyclerView.ViewHolder {
75 | @BindView(R.id.text_name) TextView nameView;
76 | @BindView(R.id.img_icon) ImageView iconView;
77 |
78 | public ContentHolder(View itemView) {
79 | super(itemView);
80 | ButterKnife.bind(this, itemView);
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/event/ControlEvent.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui.event;
2 |
3 | import com.vmloft.develop.app.screencast.entity.AVTransportInfo;
4 | import com.vmloft.develop.app.screencast.entity.RenderingControlInfo;
5 |
6 | /**
7 | * Created by lzan13 on 2018/4/12.
8 | * 投屏控制相关事件类
9 | */
10 | public class ControlEvent {
11 | private AVTransportInfo avtInfo;
12 | private RenderingControlInfo rcInfo;
13 |
14 | public AVTransportInfo getAvtInfo() {
15 | return avtInfo;
16 | }
17 |
18 | public void setAvtInfo(AVTransportInfo avtInfo) {
19 | this.avtInfo = avtInfo;
20 | }
21 |
22 | public RenderingControlInfo getRcInfo() {
23 | return rcInfo;
24 | }
25 |
26 | public void setRcInfo(RenderingControlInfo rcInfo) {
27 | this.rcInfo = rcInfo;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/event/DIDLEvent.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui.event;
2 |
3 | import org.fourthline.cling.support.model.DIDLContent;
4 |
5 | /**
6 | * Created by lzan13 on 2018/3/19.
7 | * DLNA 本地媒体服务内容变化实践类
8 | */
9 | public class DIDLEvent {
10 | public DIDLContent content;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/ui/event/DeviceEvent.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.ui.event;
2 |
3 | import com.vmloft.develop.app.screencast.entity.ClingDevice;
4 |
5 | /**
6 | * Created by lzan13 on 2018/3/9.
7 | * 设备相关事件类
8 | */
9 | public class DeviceEvent {
10 | private ClingDevice clingDevice;
11 | // 变化类型,
12 | private int type = 0;
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/vmloft/develop/app/screencast/utils/ClingUtil.java:
--------------------------------------------------------------------------------
1 | package com.vmloft.develop.app.screencast.utils;
2 |
3 | import com.vmloft.develop.app.screencast.entity.RemoteItem;
4 | import com.vmloft.develop.library.tools.utils.VMLog;
5 |
6 | import org.fourthline.cling.support.model.ProtocolInfo;
7 | import org.fourthline.cling.support.model.Res;
8 | import org.fourthline.cling.support.model.item.VideoItem;
9 | import org.seamless.util.MimeType;
10 |
11 | import java.text.SimpleDateFormat;
12 | import java.util.Date;
13 |
14 | /**
15 | * Created by lzan13 on 2018/3/27.
16 | * Cling 相关工具类
17 | */
18 | public class ClingUtil {
19 |
20 | public static String DIDL_LITE_HEADER = "";
21 | public static String DIDL_LITE_FOOTER = "";
22 |
23 | /**
24 | * 获取 Item 资源的 metadata
25 | */
26 | public static String getItemMetadata(RemoteItem remoteItem) {
27 | Res itemRes = new Res(new MimeType(ProtocolInfo.WILDCARD, ProtocolInfo.WILDCARD), remoteItem
28 | .getSize(), remoteItem.getUrl());
29 | itemRes.setDuration(remoteItem.getDuration());
30 | itemRes.setResolution(remoteItem.getResolution());
31 | VideoItem item = new VideoItem(remoteItem.getId(), "0", remoteItem.getTitle(), remoteItem.getCreator(), itemRes);
32 |
33 | StringBuilder metadata = new StringBuilder();
34 | metadata.append(DIDL_LITE_HEADER);
35 |
36 | metadata.append(String.format("", item.getId(), item
37 | .getParentID(), item.isRestricted() ? "1" : "0"));
38 |
39 | metadata.append(String.format("%s", item.getTitle()));
40 | String creator = item.getCreator();
41 | if (creator != null) {
42 | creator = creator.replaceAll("<", "_");
43 | creator = creator.replaceAll(">", "_");
44 | }
45 | metadata.append(String.format("%s", item.getClazz().getValue()));
46 | metadata.append(String.format("%s", creator));
47 |
48 | // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
49 | // Date now = new Date();
50 | // String time = sdf.format(now);
51 | // metadata.append(String.format("%s", time));
52 |
53 | // metadata.append(String.format("%s",
54 | // localItem.get);
55 |
56 | // http://192.168.1.104:8088/Music/07.我醒著做夢.mp3
58 |
59 | Res res = item.getFirstResource();
60 | if (res != null) {
61 | // protocol info
62 | String protocolInfo = "";
63 | ProtocolInfo pi = res.getProtocolInfo();
64 | if (pi != null) {
65 | protocolInfo = String.format("protocolInfo=\"%s:%s:%s:%s\"", pi.getProtocol(), pi.getNetwork(), pi
66 | .getContentFormatMimeType(), pi.getAdditionalInfo());
67 | }
68 | VMLog.i("protocolInfo: " + protocolInfo);
69 |
70 | // resolution, extra info, not adding yet
71 | String resolution = "";
72 | if (res.getResolution() != null && res.getResolution().length() > 0) {
73 | resolution = String.format("resolution=\"%s\"", res.getResolution());
74 | }
75 |
76 | // duration
77 | String duration = "";
78 | if (res.getDuration() != null && res.getDuration().length() > 0) {
79 | duration = String.format("duration=\"%s\"", res.getDuration());
80 | }
81 |
82 | // res begin
83 | // metadata.append(String.format("", protocolInfo)); // no resolution & duration yet
84 | metadata.append(String.format("", protocolInfo, resolution, duration));
85 |
86 | // url
87 | String url = res.getValue();
88 | metadata.append(url);
89 |
90 | // res end
91 | metadata.append("");
92 | }
93 | metadata.append("
");
94 | metadata.append(DIDL_LITE_FOOTER);
95 | return metadata.toString();
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_back_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_cast_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_done_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_folder_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pause_circle_outline_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_circle_outline_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_power_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_refresh_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_skip_next_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_skip_previous_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_stop_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_volume_off_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_volume_up_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_device_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
20 |
21 |
29 |
30 |
31 |
36 |
37 |
42 |
43 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
20 |
21 |
29 |
30 |
31 |
35 |
36 |
41 |
42 |
52 |
53 |
59 |
60 |
64 |
65 |
66 |
67 |
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_media_play.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
20 |
21 |
29 |
30 |
31 |
36 |
37 |
38 |
42 |
43 |
48 |
49 |
55 |
56 |
63 |
64 |
71 |
72 |
73 |
74 |
75 |
78 |
79 |
80 |
86 |
87 |
93 |
94 |
100 |
101 |
102 |
107 |
108 |
109 |
113 |
114 |
122 |
123 |
132 |
133 |
142 |
143 |
144 |
145 |
150 |
151 |
158 |
159 |
166 |
167 |
176 |
177 |
186 |
187 |
188 |
189 |
190 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_local_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
20 |
21 |
26 |
27 |
28 |
32 |
33 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_remote_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/framgnet_device_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_common_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
15 |
16 |
24 |
25 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/play_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzan13/VMScreenCast/b4f41ce48f3309c336be58a2452cd02e37d49dc6/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzan13/VMScreenCast/b4f41ce48f3309c336be58a2452cd02e37d49dc6/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzan13/VMScreenCast/b4f41ce48f3309c336be58a2452cd02e37d49dc6/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzan13/VMScreenCast/b4f41ce48f3309c336be58a2452cd02e37d49dc6/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzan13/VMScreenCast/b4f41ce48f3309c336be58a2452cd02e37d49dc6/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzan13/VMScreenCast/b4f41ce48f3309c336be58a2452cd02e37d49dc6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzan13/VMScreenCast/b4f41ce48f3309c336be58a2452cd02e37d49dc6/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzan13/VMScreenCast/b4f41ce48f3309c336be58a2452cd02e37d49dc6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzan13/VMScreenCast/b4f41ce48f3309c336be58a2452cd02e37d49dc6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzan13/VMScreenCast/b4f41ce48f3309c336be58a2452cd02e37d49dc6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @color/vm_theme_primary
4 | @color/vm_theme_primary_dark
5 | @color/vm_theme_accent
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | VMScreenCast
3 |
4 | 投屏设备
5 | 当前投屏资源:
6 | 投屏播放控制
7 | 选择要投屏的设备:
8 | 上级目录
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
10 |
11 |
14 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 |
4 | repositories {
5 | google()
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.1.2'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | google()
19 | jcenter()
20 | mavenCentral()
21 | maven {
22 | url 'http://4thline.org/m2'
23 | }
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzan13/VMScreenCast/b4f41ce48f3309c336be58a2452cd02e37d49dc6/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jun 20 19:27:08 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
--------------------------------------------------------------------------------