现在的位置: 首页 > 综合 > 正文

智能电视TV开发—客户端和服务器通信

2018年02月16日 ⁄ 综合 ⁄ 共 11650字 ⁄ 字号 评论关闭

在做智能电视应用的时候,最头疼的就是焦点问题,特别是对于个人开发者,没有设备这是最最头疼的事情了,在没有设备的情况下,怎么实现智能电视应用呢,接下来我是用TV程序来做演示的,所以接下来的所有操作是在有网络的情况下,TV链接到一个路由器上面,做过开发的人都知道Socket编程分为两种一个是可靠传输的TCP,另一个是不可靠传输的UDP,TCP需要知道对方的IP才能实现,UDP虽然不可靠,但是它可以实现广播来进行通信,从而得知对方的IP地址,然后就可以TCP通信了,对于智能电视的TV开发,如果你没有设备,则可以利用UDP的这个特性来实现手机操控电视,建立通信协议,然后TV端Server接收广播,手机端作为Client发送广播,所有的操作放在手机端来实现,TV只接收并处理相应的命令。

一、UDP实现

首先就是实现UDP的广播通信,下面就是UDP的Server和Client代码:
Server:为了实现能够长时间的接收客户端的信息,所以要把Server端放在线程里面如下:

/**
	 * 实现后台监听广播
	 * @author jwzhangjie
	 */
	private class UdpServerRunable implements  Runnable {
		@Override
		public void run() {
			byte[] data = new byte[256];
			DatagramPacket udpPacket = new DatagramPacket(data, 256);
			try {
				udpSocket = new DatagramSocket(43708);
			} catch (Exception e) {
				e.printStackTrace();
			}
			while (!isStop) {
				try {
					udpSocket.receive(udpPacket);
					if (udpPacket.getLength() != 0) {
						Url = new String(data, 0, udpPacket.getLength());
						Log.e(TAG, Url);
						if (onUdpServerCallBackListener != null) {
							onUdpServerCallBackListener.onPlayUrl(Url);
						}
					}
				} catch (Exception e) {
				}
			}
		}
	};

为了测试方便我先阶段是Client放在PC端来实现的,为了实现循环测试,我也是把客户端放在一个线程里面,代码如下:

public class Test_UDP_Client{
	
	public static void main(String[] args){
		new Thread(new Runnable() {
			int i = 0;
			private byte[] buffer = new byte[40];
			
			@Override
			public void run() {
				DatagramPacket dataPacket = null;
				DatagramSocket udpSocket = null;
				List<String> listData = new ArrayList<String>();
				listData.add("http://live.gslb.letv.com/gslb?stream_id=hunan&tag=live&ext=m3u8&sign=live_tv");
				listData.add("http://play.api.pptv.com/web-m3u8-300161.m3u8?type=m3u8.web.pad");
				try {
					udpSocket = new DatagramSocket(43708);
					dataPacket = new DatagramPacket(buffer, 40);
				    dataPacket.setPort(43708);   
				    InetAddress broadcastAddr;
				    broadcastAddr = InetAddress.getByName("255.255.255.255");
				    dataPacket.setAddress(broadcastAddr);
				} catch (Exception e) {
				}
				while (i < 30) {
					i++;
					try {
						byte[] data = (listData.get(i%2)).getBytes();
					    dataPacket.setData( data );
					    dataPacket.setLength( data.length );
						udpSocket.send(dataPacket);
						Thread.sleep(20000);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				udpSocket.close();
			}
		}).start();
	}
}

、Service启动Server的线程

线程是不可控的,如果Activity突然的挂掉,那么这个线程还是在后台运行的,所以我们要把Server放在Service里面,通过Service来启动服务端,代码如下:

package com.jwzhangjie.smart_tv.server;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

import com.jwzhangjie.smart_tv.interfaces.UdpServerCallBackListener;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class CommandServer extends Service{

	private static String TAG = CommandServer.class.getName();
	public static boolean isStop = false;
	private DatagramSocket udpSocket = null;
	private Thread udpServerThread;
	private String Url;
	/**
	 * 设置视频连接的回调接口
	 */
	private UdpServerCallBackListener onUdpServerCallBackListener;
	
	@Override
	public IBinder onBind(Intent intent) {
		Log.e(TAG, "onBind");
		startListener();
		return new LocalBinder();
	}
	/**
	 * 注册回调接口的方法,供外部调用
	 * @param onUdpServerCallBackListener
	 */
	public void setOnUdpServerCallBackListener(UdpServerCallBackListener onUdpServerCallBackListener){
		this.onUdpServerCallBackListener = onUdpServerCallBackListener;
	}
	@Override
	public void onCreate() {
		Log.e(TAG, "onCreate");
		super.onCreate();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.e(TAG, "onStartCommand");
		startListener();
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public void onDestroy() {
		Log.e(TAG, "onDestroy");
		isStop = true;
		udpSocket.disconnect();
		udpSocket.close();
		udpServerThread.interrupt();
		udpServerThread = null;
		super.onDestroy();
	}

	@Override
	public boolean onUnbind(Intent intent) {
		Log.e(TAG, "onUnbind");
		return super.onUnbind(intent);
	}

	/**
	 * 开始监听广播
	 */
	private void startListener(){
		if (udpServerThread == null) {
			Log.e(TAG, "run");
			udpServerThread = new Thread(new UdpServerRunable());
			udpServerThread.start();
		}
	}
	/**
	 * 实现后台监听广播
	 * @author pig_video
	 */
	private class UdpServerRunable implements  Runnable {
		@Override
		public void run() {
			byte[] data = new byte[256];
			DatagramPacket udpPacket = new DatagramPacket(data, 256);
			try {
				udpSocket = new DatagramSocket(43708);
			} catch (Exception e) {
				e.printStackTrace();
			}
			while (!isStop) {
				try {
					udpSocket.receive(udpPacket);
					if (udpPacket.getLength() != 0) {
						Url = new String(data, 0, udpPacket.getLength());
						Log.e(TAG, Url);
						if (onUdpServerCallBackListener != null) {
							onUdpServerCallBackListener.onPlayUrl(Url);
						}
					}
				} catch (Exception e) {
				}
			}
		}
	};

	//定义内部类继承Binder
	public class LocalBinder extends Binder{
		//返回本地服务
		public CommandServer getService(){
			return CommandServer.this;
		}
	}
}

3.Activity与Service的绑定

我这里启动Service的方式是通过Activity的onBind来启动的,当Activity关闭的时候,也将Service关闭同时关闭Server的线程,当然常驻后台也行,不过用户可能不太喜欢,毕竟需要资源,播放器我选用的是免费的Vitamio主要是他们把上层应用的代码也提供出来了,非常省事。

package com.jwzhangjie.smart_tv.player;

import io.vov.vitamio.LibsChecker;
import io.vov.vitamio.MediaPlayer;
import io.vov.vitamio.MediaPlayer.OnBufferingUpdateListener;
import io.vov.vitamio.MediaPlayer.OnErrorListener;
import io.vov.vitamio.MediaPlayer.OnInfoListener;
import io.vov.vitamio.MediaPlayer.OnTimedTextListener;
import io.vov.vitamio.widget.MediaController;
import io.vov.vitamio.widget.VideoView;

import com.jwzhangjie.smart_tv.R;
import com.jwzhangjie.smart_tv.dialog.JWDialogLoading;
import com.jwzhangjie.smart_tv.interfaces.UdpServerCallBackListener;
import com.jwzhangjie.smart_tv.server.CommandServer;

import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;

public class SmartTV_Server extends Activity implements OnInfoListener, OnBufferingUpdateListener{
	
	private static final String TAG = SmartTV_Server.class.getName();
	private String path = "http://live.gslb.letv.com/gslb?stream_id=guangdong&tag=live&ext=m3u8";
	private String subtitle_path = "";
	private VideoView mVideoView;
	private TextView mSubtitleView;
	private long mPosition = 0;
	private int mVideoLayout = 0;
	private JWDialogLoading mDialogLoading;
	private CommandServer pigBackServices;
	private boolean isFirst = true;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		if (!LibsChecker.checkVitamioLibs(this))
			return;
		setContentView(R.layout.subtitle2);
		mDialogLoading = new JWDialogLoading(this, R.style.dialog);
		//绑定后台接收视频连接的Service
		Intent intent = new Intent(SmartTV_Server.this, CommandServer.class);
		bindService(intent, conn, Context.BIND_AUTO_CREATE);
		mVideoView = (VideoView) findViewById(R.id.surface_view);
		mSubtitleView = (TextView) findViewById(R.id.subtitle_view);
		
		if (path == "") {
			// Tell the user to provide a media file URL/path.
			Toast.makeText(SmartTV_Server.this, "Please select video, and set path" + " variable to your media file URL/path", Toast.LENGTH_LONG).show();
			return;
		} else {
			/*
			 * Alternatively,for streaming media you can use
			 * mVideoView.setVideoURI(Uri.parse(URLstring));
			 */
			isFirst = false;
			mVideoView.setVideoPath(path);

			mVideoView.setMediaController(new MediaController(this));
			mVideoView.requestFocus();
			mVideoView.setOnErrorListener(new OnErrorListener() {
				@Override
				public boolean onError(MediaPlayer mp, int what, int extra) {
					return true;
				}
			});
			mVideoView.setOnInfoListener(this);
			mVideoView.setOnBufferingUpdateListener(this);
			mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
				@Override
				public void onPrepared(MediaPlayer mediaPlayer) {
					// optional need Vitamio 4.0
					mediaPlayer.setPlaybackSpeed(1.0f);
					mVideoView.addTimedTextSource(subtitle_path);
					mVideoView.setTimedTextShown(true);
				}
			});
			mVideoView.setOnTimedTextListener(new OnTimedTextListener() {
				@Override
				public void onTimedText(String text) {
					mSubtitleView.setText(text);
				}
				@Override
				public void onTimedTextUpdate(byte[] pixels, int width, int height) {
				}
			});
		}
	}
	@Override
	protected void onPause() {
		mPosition = mVideoView.getCurrentPosition();
		mVideoView.stopPlayback();
		super.onPause();
	}

	@Override
	protected void onResume() {
		if (mPosition > 0) {
			mVideoView.seekTo(mPosition);
			mPosition = 0;
		}
		super.onResume();
		mVideoView.start();
	}

	public void changeLayout(View view) {
		mVideoLayout++;
		if (mVideoLayout == 4) {
			mVideoLayout = 0;
		}
		switch (mVideoLayout) {
		case 0:
			mVideoLayout = VideoView.VIDEO_LAYOUT_ORIGIN;
			view.setBackgroundResource(R.drawable.mediacontroller_sreen_size_100);
			break;
		case 1:
			mVideoLayout = VideoView.VIDEO_LAYOUT_SCALE;
			view.setBackgroundResource(R.drawable.mediacontroller_screen_fit);
			break;
		case 2:
			mVideoLayout = VideoView.VIDEO_LAYOUT_STRETCH;
			view.setBackgroundResource(R.drawable.mediacontroller_screen_size);
			break;
		case 3:
			mVideoLayout = VideoView.VIDEO_LAYOUT_ZOOM;
			view.setBackgroundResource(R.drawable.mediacontroller_sreen_size_crop);
			break;
		}
		mVideoView.setVideoLayout(mVideoLayout, 0);
	}
	ServiceConnection conn = new ServiceConnection() {
		@Override
		public void onServiceDisconnected(ComponentName name) {
			pigBackServices = null;
		}
		
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			pigBackServices = ((CommandServer.LocalBinder)service).getService();
			pigBackServices.setOnUdpServerCallBackListener(new UdpServerCallBackListener() {
				@Override
				public void onPlayUrl(String url) {
					path = url;
					if (isFirst) {
						isFirst = false;
						mVideoView.setVideoPath(url);

						mVideoView.setMediaController(new MediaController(SmartTV_Server.this));
						mVideoView.requestFocus();

						mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
							@Override
							public void onPrepared(MediaPlayer mediaPlayer) {
								// optional need Vitamio 4.0
								mediaPlayer.setPlaybackSpeed(1.0f);
								mVideoView.addTimedTextSource(subtitle_path);
								mVideoView.setTimedTextShown(true);
							}
						});
						mVideoView.setOnTimedTextListener(new OnTimedTextListener() {
							@Override
							public void onTimedText(String text) {
								mSubtitleView.setText(text);
							}
							@Override
							public void onTimedTextUpdate(byte[] pixels, int width, int height) {
							}
						});
					}else{
						if (mVideoView.isPlaying()) {
							mVideoView.stopPlayback();
						}
						mVideoView.setVideoPath(url);
					}
					Log.e(TAG, url);
				}
			});
		}
	};
	
	@Override
	protected void onDestroy() {
		if (pigBackServices != null) {
			unbindService(conn);
		}
		super.onDestroy();
	}
	@Override
	public void onBufferingUpdate(MediaPlayer mp, int percent) {
		mDialogLoading.setProgreess(percent);
	}
	private boolean isStart;
	@Override
	public boolean onInfo(MediaPlayer mp, int what, int extra) {
		switch (what) {
		case MediaPlayer.MEDIA_INFO_BUFFERING_START:
			if (mVideoView.isPlaying()) {
				mVideoView.pause();
				isStart = true;
				if (!mDialogLoading.isShowing()) {
					mDialogLoading.show();
				}
			}
			break;
		case MediaPlayer.MEDIA_INFO_BUFFERING_END:
			if (isStart) {
				mVideoView.start();
				if (mDialogLoading.isShowing()) {
					mDialogLoading.dismiss();
				}
			}
			break;
		case MediaPlayer.MEDIA_INFO_DOWNLOAD_RATE_CHANGED:
			mDialogLoading.setValue("" + extra + "kb/s" + "  ");
			break;
		}
		return true;
	}
	
}

4.加载进度

可能你在上面的播放器代码里面会看到JWDialogLoading,这个是一个网上的环形进度框,能够提示视频的记载进度和下载速度,不过你要覆写Vitamio里面的OnInfoListener, OnBufferingUpdateListener这两个接口才行。

package com.jwzhangjie.smart_tv.dialog;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.view.Display;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.widget.LinearLayout;

public class JWDialogLoading extends Dialog{

	private RadialProgressWidget mProgressWidget;
	private Activity context;
	public JWDialogLoading(Activity context) {
		super(context);
		this.context = context;
		mProgressWidget = new RadialProgressWidget(context);
	}
	
	public JWDialogLoading(Activity context, int style) {
		super(context, style);
		this.context = context;
		mProgressWidget = new RadialProgressWidget(context);
	}

	@SuppressWarnings("deprecation")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		LinearLayout.LayoutParams param = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
		setContentView(mProgressWidget, param);
		mProgressWidget.setSecondaryText("Loading...");
		mProgressWidget.setTouchEnabled(false); 
		Window window = getWindow();
		LayoutParams params = window.getAttributes();
		Display display = context.getWindowManager().getDefaultDisplay();
		params.height = (int)(display.getWidth()*0.3);
		params.width = (int)(display.getWidth()*0.3);
		params.alpha = 1.0f;
		window.setAttributes(params);
	}
	
	public void setProgreess(int value){
		mProgressWidget.setCurrentValue(value);
		mProgressWidget.invalidate();
		if (value == 100) {
			dismiss();
		}
	}
	
	public void setValue(String content){
		mProgressWidget.setSecondaryText("Loading...  "+content);
	}

}

5.效果图如下:



抱歉!评论已关闭.