blob: 4ea7852d72f079d620a559123816aacc01b89397 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.atsctvinput;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.media.MediaPlayer;
import android.media.tv.TvContract;
import android.media.tv.TvInputService;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import android.util.Pair;
import android.view.Surface;
import com.example.android.atsctvinput.SampleTsStream.TsStream;
import com.example.android.atsctvinput.SectionParser.EITItem;
import com.example.android.atsctvinput.SectionParser.VCTItem;
import java.util.List;
/**
* A sample TvInputService which plays ATSC TV stream.
*/
public class AtscTvInputService extends TvInputService {
private static final String TAG = "AtscTvInputService";
private static final int SEC_IN_MS = 1000;
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate()");
// TODO: Uncomment or remove when a new API design is locked down.
// setAvailable(true);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy()");
}
@Override
public TvInputService.Session onCreateSession(String inputId) {
return new MyTvInputSessionImpl();
}
public TsStream getTsStreamForChannel(Uri channelUri) {
String[] projection = { TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA };
if (channelUri == null) {
return null;
}
Cursor cursor = this.getContentResolver().query(
channelUri, projection, null, null, null);
if (cursor == null) {
return null;
}
if (cursor.getCount() < 1) {
cursor.close();
return null;
}
cursor.moveToNext();
TsStream stream = SampleTsStream.getTsStreamFromTuneInfo(cursor.getString(0));
cursor.close();
return stream;
}
private class MyTvInputSessionImpl extends TvInputService.Session {
private MediaPlayer mPlayer;
protected MyTvInputSessionImpl() {
mPlayer = new MediaPlayer();
}
@Override
public void onRelease() {
if (mPlayer != null) {
mPlayer.release();
mPlayer = null;
}
}
@Override
public boolean onSetSurface(Surface surface) {
Log.d(TAG, "onSetSurface(" + surface + ")");
mPlayer.setSurface(surface);
return true;
}
@Override
public void onSetStreamVolume(float volume) {
Log.d(TAG, "onSetStreamVolume(" + volume + ")");
mPlayer.setVolume(volume, volume);
}
@Override
public boolean onTune(Uri channelUri) {
Log.d(TAG, "onTune(" + channelUri + ")");
mPlayer.reset();
TsStream stream = getTsStreamForChannel(channelUri);
if (stream == null) {
return false;
}
new ProgramUpdateTask().execute(stream, channelUri);
AssetFileDescriptor afd = getResources().openRawResourceFd(stream.mResourceId);
try {
mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
afd.getLength());
mPlayer.prepare();
afd.close();
} catch (Exception e) {
Log.e(TAG, "Failed to tune to(" + channelUri + ")");
mPlayer.reset();
return false;
}
mPlayer.start();
return true;
}
@Override
public void onSetCaptionEnabled(boolean enabled) {
Log.d(TAG, "onSetCaptionEnabled(" + enabled + ")");
}
}
private class ProgramUpdateTask extends AsyncTask<Object, Void, Void> {
@Override
protected Void doInBackground(Object... objs) {
TsStream stream = (TsStream) objs[0];
Uri channelUri = (Uri) objs[1];
Pair<VCTItem, List<EITItem>> result =
SampleTsStream.extractChannelInfo(AtscTvInputService.this, stream);
if (result == null) {
return null;
}
clearPrograms(channelUri);
// The sample streams have passed timestamps. In order to show the metadata properly in
// TV app, we offset the time here.
long timeOffsetMs = Long.MIN_VALUE;
long currentTimeMs = System.currentTimeMillis();
for (EITItem i : result.second) {
if (timeOffsetMs == Long.MIN_VALUE) {
timeOffsetMs = currentTimeMs - i.getStartTime() * SEC_IN_MS;
}
insertProgram(channelUri, i, timeOffsetMs);
}
return null;
}
private void clearPrograms(Uri channelUri) {
Uri uri = TvContract.buildProgramsUriForChannel(channelUri);
getContentResolver().delete(uri, null, null);
}
private Uri insertProgram(Uri channelUri, EITItem event, long timeOffsetMs) {
Log.d(TAG, "insertProgram " + event.getTitleText());
ContentValues values = new ContentValues();
values.put(TvContract.Programs.COLUMN_CHANNEL_ID, ContentUris.parseId(channelUri));
values.put(TvContract.Programs.COLUMN_TITLE, event.getTitleText());
values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, timeOffsetMs
+ event.getStartTime() * SEC_IN_MS);
values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, timeOffsetMs
+ (event.getStartTime() + event.getLengthInSecond()) * SEC_IN_MS);
return getContentResolver().insert(
TvContract.Programs.CONTENT_URI, values);
}
}
}