Repository: youtube/yt-direct-lite-android
Branch: master
Commit: 9fc42e0993a7
Files: 44
Total size: 122.9 KB
Directory structure:
gitextract_g6krwkb4/
├── .classpath
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE-2.0.txt
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── libs/
│ │ └── YouTubeAndroidPlayerApi.jar
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── google/
│ │ └── ytdl/
│ │ ├── Auth.java
│ │ ├── Constants.java
│ │ ├── MainActivity.java
│ │ ├── PlayActivity.java
│ │ ├── ResumableUpload.java
│ │ ├── ReviewActivity.java
│ │ ├── UploadService.java
│ │ ├── UploadsListFragment.java
│ │ └── util/
│ │ ├── LruBitmapCache.java
│ │ ├── NetworkSingleton.java
│ │ ├── Upload.java
│ │ ├── Utils.java
│ │ └── VideoData.java
│ └── res/
│ ├── drawable/
│ │ └── list_divider_horizontal_inset.xml
│ ├── layout/
│ │ ├── activity_main.xml
│ │ ├── activity_play.xml
│ │ ├── activity_review.xml
│ │ ├── developer_setup_required.xml
│ │ ├── list_fragment.xml
│ │ └── list_item.xml
│ ├── menu/
│ │ ├── activity_main.xml
│ │ ├── play.xml
│ │ └── review.xml
│ └── values/
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── yt-direct-lite-android.iml
================================================
FILE CONTENTS
================================================
================================================
FILE: .classpath
================================================
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
================================================
FILE: .gitignore
================================================
.gradle
.DS_Store
YouTubeDirectLiteforAndroid.iml
.idea
build
local.properties
================================================
FILE: CONTRIBUTING.md
================================================
# How to contribute #
We'd love to accept your patches and contributions to this project. There are
a just a few small guidelines you need to follow.
## Contributor License Agreement ##
Contributions to any Google project must be accompanied by a Contributor
License Agreement. This is not a copyright **assignment**, it simply gives
Google permission to use and redistribute your contributions as part of the
project.
* If you are an individual writing original source code and you're sure you
own the intellectual property, then you'll need to sign an [individual
CLA][].
* If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a [corporate CLA][].
You generally only need to submit a CLA once, so if you've already submitted
one (even if it was for a different project), you probably don't need to do it
again.
[individual CLA]: https://developers.google.com/open-source/cla/individual
[corporate CLA]: https://developers.google.com/open-source/cla/corporate
## Submitting a patch ##
1. It's generally best to start by opening a new issue describing the bug or
feature you're intending to fix. Even if you think it's relatively minor,
it's helpful to know what people are working on. Mention in the initial
issue that you are planning to work on that bug or feature so that it can
be assigned to you.
1. Follow the normal process of [forking][] the project, and setup a new
branch to work in. It's important that each group of changes be done in
separate branches in order to ensure that a pull request only includes the
commits related to that bug or feature.
1. Go makes it very simple to ensure properly formatted code, so always run
`go fmt` on your code before committing it. You should also run
[golint][] over your code. As noted in the [golint readme][], it's not
strictly necessary that your code be completely "lint-free", but this will
help you find common style issues.
1. Any significant changes should almost always be accompanied by tests. The
project already has good test coverage, so look at some of the existing
tests if you're unsure how to go about it. [gocov][] and [gocov-html][]
are invaluable tools for seeing which parts of your code aren't being
exercised by your tests.
1. Do your best to have [well-formed commit messages][] for each change.
This provides consistency throughout the project, and ensures that commit
messages are able to be formatted properly by various git tools.
1. Finally, push the commits to your fork and submit a [pull request][].
[forking]: https://help.github.com/articles/fork-a-repo
[golint]: https://github.com/golang/lint
[golint readme]: https://github.com/golang/lint/blob/master/README
[gocov]: https://github.com/axw/gocov
[gocov-html]: https://github.com/matm/gocov-html
[well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
[pull request]: https://help.github.com/articles/creating-a-pull-request
## Other notes on code organization ##
Currently, everything is defined in the main `github` package, with API methods
broken into separate service objects. These services map directly to how
the [GitHub API documentation][] is organized, so use that as your guide for
where to put new methods.
Sub-service (e.g. [Repo Hooks][]) implementations are split into separate files
based on the APIs they provide. These files are named service_api.go (e.g.
repos_hooks.go) to describe the API to service mappings.
[GitHub API documentation]: http://developer.github.com/v3/
[Repo Hooks]: http://developer.github.com/v3/repos/hooks/
================================================
FILE: LICENSE-2.0.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
================================================
FILE: README.md
================================================
YouTube Direct Lite for Android
===========
The code is a reference implementation for an Android OS application that captures video, uploads it to YouTube, and submits the video to a [YouTube Direct Lite](http://code.google.com/p/youtube-direct-lite/) instance.
For more information, you can read the [Youtube API blog post](http://apiblog.youtube.com/2013/08/heres-my-playlist-so-submit-video-maybe.html).
This application utilizes [YouTube Data API v3](https://developers.google.com/youtube/v3/) , [YouTube Android Player API](https://developers.google.com/youtube/android/player/), [YouTube Resumable Uploads](https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol?hl=en), [Google Play Services](https://developer.android.com/google/play-services/index.html) and [Plus API](https://developers.google.com/+/mobile/android/Google).
To use this application,
1. In your [Google Developers Console](https://console.developers.google.com),
1. Enable the YouTube Data API v3 and Google+ API.
1. Create a client ID for Android, using your SHA1 and package name.
1. [Register your Android app](https://developers.google.com/youtube/android/player/register)
1. Plug in your Playlist Id into Constants.java and Android API Key into Auth.java




================================================
FILE: app/.gitignore
================================================
/build
app.iml
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.0"
defaultConfig {
applicationId "com.google.ytdl"
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.android.gms:play-services-plus:7.8.0'
compile 'com.android.support:support-v13:23.0.0'
compile 'com.google.apis:google-api-services-youtube:v3-rev120-1.19.0'
compile 'com.google.http-client:google-http-client-android:+'
compile 'com.google.api-client:google-api-client-android:+'
compile 'com.google.api-client:google-api-client-gson:+'
compile 'com.google.code.gson:gson:2.2.4'
compile 'com.mcxiaoke.volley:library:1.0.18'
compile files('libs/YouTubeAndroidPlayerApi.jar')
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/ulukaya/android-sdks/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.ytdl"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="23" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name="com.google.ytdl.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.google.ytdl.UploadService"
android:exported="true" />
<activity
android:name="com.google.ytdl.PlayActivity"
android:label="@string/title_activity_play"
android:parentActivityName="com.google.ytdl.MainActivity"></activity>
<activity
android:name="com.google.ytdl.ReviewActivity"
android:label="@string/title_activity_review"
android:parentActivityName="com.google.ytdl.MainActivity"></activity>
</application>
</manifest>
================================================
FILE: app/src/main/java/com/google/ytdl/Auth.java
================================================
/*
* Copyright (c) 2013 Google Inc.
*
* 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.google.ytdl;
import com.google.android.gms.common.Scopes;
import com.google.api.services.youtube.YouTubeScopes;
public class Auth {
// Register an API key here: https://console.developers.google.com
public static final String KEY = "Replace me with your API key";
public static final String[] SCOPES = {Scopes.PROFILE, YouTubeScopes.YOUTUBE};
}
================================================
FILE: app/src/main/java/com/google/ytdl/Constants.java
================================================
/*
* Copyright (c) 2013 Google Inc.
*
* 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.google.ytdl;
/**
* @author Ibrahim Ulukaya <ulukaya@google.com>
* <p/>
* This class hold constants.
*/
public class Constants {
public static final int MAX_KEYWORD_LENGTH = 30;
public static final String DEFAULT_KEYWORD = "ytdl";
// A playlist ID is a string that begins with PL. You must replace this string with the correct
// playlist ID for the app to work
public static final String UPLOAD_PLAYLIST = "Replace me with the playlist ID you want to upload into";
public static final String APP_NAME = "ytd-android";
}
================================================
FILE: app/src/main/java/com/google/ytdl/MainActivity.java
================================================
/*
* Copyright (c) 2013 Google Inc.
*
* 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.google.ytdl;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.volley.toolbox.ImageLoader;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.model.ChannelListResponse;
import com.google.api.services.youtube.model.PlaylistItem;
import com.google.api.services.youtube.model.PlaylistItemListResponse;
import com.google.api.services.youtube.model.Video;
import com.google.api.services.youtube.model.VideoListResponse;
import com.google.api.services.youtube.model.VideoSnippet;
import com.google.ytdl.util.NetworkSingleton;
import com.google.ytdl.util.Upload;
import com.google.ytdl.util.Utils;
import com.google.ytdl.util.VideoData;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* @author Ibrahim Ulukaya <ulukaya@google.com>
* <p/>
* Main activity class which handles authorization and intents.
*/
public class MainActivity extends Activity implements
UploadsListFragment.Callbacks {
// private static final int MEDIA_TYPE_VIDEO = 7;
public static final String ACCOUNT_KEY = "accountName";
public static final String MESSAGE_KEY = "message";
public static final String YOUTUBE_ID = "youtubeId";
public static final String YOUTUBE_WATCH_URL_PREFIX = "http://www.youtube.com/watch?v=";
static final String REQUEST_AUTHORIZATION_INTENT = "com.google.example.yt.RequestAuth";
static final String REQUEST_AUTHORIZATION_INTENT_PARAM = "com.google.example.yt.RequestAuth.param";
private static final int REQUEST_GOOGLE_PLAY_SERVICES = 0;
private static final int REQUEST_GMS_ERROR_DIALOG = 1;
private static final int REQUEST_ACCOUNT_PICKER = 2;
private static final int REQUEST_AUTHORIZATION = 3;
private static final int RESULT_PICK_IMAGE_CROP = 4;
private static final int RESULT_VIDEO_CAP = 5;
private static final int REQUEST_DIRECT_TAG = 6;
private static final String TAG = "MainActivity";
final HttpTransport transport = AndroidHttp.newCompatibleTransport();
final JsonFactory jsonFactory = new GsonFactory();
GoogleAccountCredential credential;
private ImageLoader mImageLoader;
private String mChosenAccountName;
private Uri mFileURI = null;
private VideoData mVideoData;
private UploadBroadcastReceiver broadcastReceiver;
private UploadsListFragment mUploadsListFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
super.onCreate(savedInstanceState);
mUploadsListFragment = new UploadsListFragment(getApplicationContext());
// Check to see if the proper keys and playlist IDs have been set up
if (!isCorrectlyConfigured()) {
setContentView(R.layout.developer_setup_required);
showMissingConfigurations();
} else {
setContentView(R.layout.activity_main);
ensureLoader();
credential = GoogleAccountCredential.usingOAuth2(
getApplicationContext(), Arrays.asList(Auth.SCOPES));
// set exponential backoff policy
credential.setBackOff(new ExponentialBackOff());
if (savedInstanceState != null) {
mChosenAccountName = savedInstanceState.getString(ACCOUNT_KEY);
} else {
loadAccount();
}
credential.setSelectedAccountName(mChosenAccountName);
mUploadsListFragment = (UploadsListFragment) getFragmentManager()
.findFragmentById(R.id.list_fragment);
}
}
/**
* This method checks various internal states to figure out at startup time
* whether certain elements have been configured correctly by the developer.
* Checks that:
* <ul>
* <li>the API key has been configured</li>
* <li>the playlist ID has been configured</li>
* </ul>
*
* @return true if the application is correctly configured for use, false if
* not
*/
private boolean isCorrectlyConfigured() {
// This isn't going to internationalize well, but we only really need
// this for the sample app.
// Real applications will remove this section of code and ensure that
// all of these values are configured.
if (Auth.KEY.startsWith("Replace")) {
return false;
}
if (Constants.UPLOAD_PLAYLIST.startsWith("Replace")) {
return false;
}
return true;
}
/**
* This method renders the ListView explaining what the configurations the
* developer of this application has to complete. Typically, these are
* static variables defined in {@link Auth} and {@link Constants}.
*/
private void showMissingConfigurations() {
List<MissingConfig> missingConfigs = new ArrayList<MissingConfig>();
// Make sure an API key is registered
if (Auth.KEY.startsWith("Replace")) {
missingConfigs
.add(new MissingConfig(
"API key not configured",
"KEY constant in Auth.java must be configured with your Simple API key from the Google API Console"));
}
// Make sure a playlist ID is registered
if (Constants.UPLOAD_PLAYLIST.startsWith("Replace")) {
missingConfigs
.add(new MissingConfig(
"Playlist ID not configured",
"UPLOAD_PLAYLIST constant in Constants.java must be configured with a Playlist ID to submit to. (The playlist ID typically has a prexix of PL)"));
}
// Renders a simple_list_item_2, which consists of a title and a body
// element
ListAdapter adapter = new ArrayAdapter<MissingConfig>(this,
android.R.layout.simple_list_item_2, missingConfigs) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View row;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getApplicationContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(android.R.layout.simple_list_item_2,
null);
} else {
row = convertView;
}
TextView titleView = (TextView) row
.findViewById(android.R.id.text1);
TextView bodyView = (TextView) row
.findViewById(android.R.id.text2);
MissingConfig config = getItem(position);
titleView.setText(config.title);
bodyView.setText(config.body);
return row;
}
};
// Wire the data adapter up to the view
ListView missingConfigList = (ListView) findViewById(R.id.missing_config_list);
missingConfigList.setAdapter(adapter);
}
@Override
protected void onResume() {
super.onResume();
if (broadcastReceiver == null)
broadcastReceiver = new UploadBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter(
REQUEST_AUTHORIZATION_INTENT);
LocalBroadcastManager.getInstance(this).registerReceiver(
broadcastReceiver, intentFilter);
}
private void ensureLoader() {
if (mImageLoader == null) {
// Get the ImageLoader through your singleton class.
mImageLoader = NetworkSingleton.getInstance(this).getImageLoader();
}
}
private void loadAccount() {
SharedPreferences sp = PreferenceManager
.getDefaultSharedPreferences(this);
mChosenAccountName = sp.getString(ACCOUNT_KEY, null);
invalidateOptionsMenu();
}
private void saveAccount() {
SharedPreferences sp = PreferenceManager
.getDefaultSharedPreferences(this);
sp.edit().putString(ACCOUNT_KEY, mChosenAccountName).commit();
}
private void loadData() {
if (mChosenAccountName == null) {
return;
}
loadUploadedVideos();
}
@Override
protected void onPause() {
super.onPause();
if (broadcastReceiver != null) {
LocalBroadcastManager.getInstance(this).unregisterReceiver(
broadcastReceiver);
}
if (isFinishing()) {
// mHandler.removeCallbacksAndMessages(null);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.activity_main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
loadData();
break;
case R.id.menu_accounts:
chooseAccount();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_GMS_ERROR_DIALOG:
break;
case RESULT_PICK_IMAGE_CROP:
if (resultCode == RESULT_OK) {
mFileURI = data.getData();
if (mFileURI != null) {
Intent intent = new Intent(this, ReviewActivity.class);
intent.setData(mFileURI);
startActivity(intent);
}
}
break;
case RESULT_VIDEO_CAP:
if (resultCode == RESULT_OK) {
mFileURI = data.getData();
if (mFileURI != null) {
Intent intent = new Intent(this, ReviewActivity.class);
intent.setData(mFileURI);
startActivity(intent);
}
}
break;
case REQUEST_GOOGLE_PLAY_SERVICES:
if (resultCode == Activity.RESULT_OK) {
haveGooglePlayServices();
} else {
checkGooglePlayServicesAvailable();
}
break;
case REQUEST_AUTHORIZATION:
if (resultCode != Activity.RESULT_OK) {
chooseAccount();
}
break;
case REQUEST_ACCOUNT_PICKER:
if (resultCode == Activity.RESULT_OK && data != null
&& data.getExtras() != null) {
String accountName = data.getExtras().getString(
AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
mChosenAccountName = accountName;
credential.setSelectedAccountName(accountName);
saveAccount();
}
}
break;
case REQUEST_DIRECT_TAG:
if (resultCode == Activity.RESULT_OK && data != null
&& data.getExtras() != null) {
String youtubeId = data.getStringExtra(YOUTUBE_ID);
if (youtubeId.equals(mVideoData.getYouTubeId())) {
directTag(mVideoData);
}
}
break;
}
}
private void directTag(final VideoData video) {
final Video updateVideo = new Video();
VideoSnippet snippet = video
.addTags(Arrays.asList(
Constants.DEFAULT_KEYWORD,
Upload.generateKeywordFromPlaylistId(Constants.UPLOAD_PLAYLIST)));
updateVideo.setSnippet(snippet);
updateVideo.setId(video.getYouTubeId());
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
YouTube youtube = new YouTube.Builder(transport, jsonFactory,
credential).setApplicationName(Constants.APP_NAME)
.build();
try {
youtube.videos().update("snippet", updateVideo).execute();
} catch (UserRecoverableAuthIOException e) {
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
return null;
}
}.execute((Void) null);
Toast.makeText(this,
R.string.video_submitted_to_ytdl, Toast.LENGTH_LONG)
.show();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(ACCOUNT_KEY, mChosenAccountName);
}
private void loadUploadedVideos() {
if (mChosenAccountName == null) {
return;
}
setProgressBarIndeterminateVisibility(true);
new AsyncTask<Void, Void, List<VideoData>>() {
@Override
protected List<VideoData> doInBackground(Void... voids) {
YouTube youtube = new YouTube.Builder(transport, jsonFactory,
credential).setApplicationName(Constants.APP_NAME)
.build();
try {
/*
* Now that the user is authenticated, the app makes a
* channels list request to get the authenticated user's
* channel. Returned with that data is the playlist id for
* the uploaded videos.
* https://developers.google.com/youtube
* /v3/docs/channels/list
*/
ChannelListResponse clr = youtube.channels()
.list("contentDetails").setMine(true).execute();
// Get the user's uploads playlist's id from channel list
// response
String uploadsPlaylistId = clr.getItems().get(0)
.getContentDetails().getRelatedPlaylists()
.getUploads();
List<VideoData> videos = new ArrayList<VideoData>();
// Get videos from user's upload playlist with a playlist
// items list request
PlaylistItemListResponse pilr = youtube.playlistItems()
.list("id,contentDetails")
.setPlaylistId(uploadsPlaylistId)
.setMaxResults(20l).execute();
List<String> videoIds = new ArrayList<String>();
// Iterate over playlist item list response to get uploaded
// videos' ids.
for (PlaylistItem item : pilr.getItems()) {
videoIds.add(item.getContentDetails().getVideoId());
}
// Get details of uploaded videos with a videos list
// request.
VideoListResponse vlr = youtube.videos()
.list("id,snippet,status")
.setId(TextUtils.join(",", videoIds)).execute();
// Add only the public videos to the local videos list.
for (Video video : vlr.getItems()) {
if ("public".equals(video.getStatus()
.getPrivacyStatus())) {
VideoData videoData = new VideoData();
videoData.setVideo(video);
videos.add(videoData);
}
}
// Sort videos by title
Collections.sort(videos, new Comparator<VideoData>() {
@Override
public int compare(VideoData videoData,
VideoData videoData2) {
return videoData.getTitle().compareTo(
videoData2.getTitle());
}
});
return videos;
} catch (final GooglePlayServicesAvailabilityIOException availabilityException) {
showGooglePlayServicesAvailabilityErrorDialog(availabilityException
.getConnectionStatusCode());
} catch (UserRecoverableAuthIOException userRecoverableException) {
startActivityForResult(
userRecoverableException.getIntent(),
REQUEST_AUTHORIZATION);
} catch (IOException e) {
Utils.logAndShow(MainActivity.this, Constants.APP_NAME, e);
}
return null;
}
@Override
protected void onPostExecute(List<VideoData> videos) {
setProgressBarIndeterminateVisibility(false);
if (videos == null) {
return;
}
mUploadsListFragment.setVideos(videos);
}
}.execute((Void) null);
}
@Override
public void onBackPressed() {
// if (mDirectFragment.popPlayerFromBackStack()) {
// super.onBackPressed();
// }
}
@Override
public ImageLoader onGetImageLoader() {
ensureLoader();
return mImageLoader;
}
@Override
public void onVideoSelected(VideoData video) {
mVideoData = video;
Intent intent = new Intent(this, PlayActivity.class);
intent.putExtra(YOUTUBE_ID, video.getYouTubeId());
startActivityForResult(intent, REQUEST_DIRECT_TAG);
}
@Override
public void onConnected(String connectedAccountName) {
// Make API requests only when the user has successfully signed in.
loadData();
}
public void pickFile(View view) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("video/*");
startActivityForResult(intent, RESULT_PICK_IMAGE_CROP);
}
public void recordVideo(View view) {
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
// Workaround for Nexus 7 Android 4.3 Intent Returning Null problem
// create a file to save the video in specific folder (this works for
// video only)
// mFileURI = getOutputMediaFile(MEDIA_TYPE_VIDEO);
// intent.putExtra(MediaStore.EXTRA_OUTPUT, mFileURI);
// set the video image quality to high
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
// start the Video Capture Intent
startActivityForResult(intent, RESULT_VIDEO_CAP);
}
public void showGooglePlayServicesAvailabilityErrorDialog(
final int connectionStatusCode) {
runOnUiThread(new Runnable() {
public void run() {
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(
connectionStatusCode, MainActivity.this,
REQUEST_GOOGLE_PLAY_SERVICES);
dialog.show();
}
});
}
/**
* Check that Google Play services APK is installed and up to date.
*/
private boolean checkGooglePlayServicesAvailable() {
final int connectionStatusCode = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(this);
if (GooglePlayServicesUtil.isUserRecoverableError(connectionStatusCode)) {
showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode);
return false;
}
return true;
}
private void haveGooglePlayServices() {
// check if there is already an account selected
if (credential.getSelectedAccountName() == null) {
// ask user to choose account
chooseAccount();
}
}
private void chooseAccount() {
startActivityForResult(credential.newChooseAccountIntent(),
REQUEST_ACCOUNT_PICKER);
}
/**
* Private class representing a missing configuration and what the developer
* can do to fix the issue.
*/
private class MissingConfig {
public final String title;
public final String body;
public MissingConfig(String title, String body) {
this.title = title;
this.body = body;
}
}
// public Uri getOutputMediaFile(int type)
// {
// // To be safe, you should check that the SDCard is mounted
// if(Environment.getExternalStorageState() != null) {
// // this works for Android 2.2 and above
// File mediaStorageDir = new
// File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
// "SMW_VIDEO");
//
// // This location works best if you want the created images to be shared
// // between applications and persist after your app has been uninstalled.
//
// // Create the storage directory if it does not exist
// if (! mediaStorageDir.exists()) {
// if (! mediaStorageDir.mkdirs()) {
// Log.d(TAG, "failed to create directory");
// return null;
// }
// }
//
// // Create a media file name
// String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
// Locale.getDefault()).format(new Date());
// File mediaFile;
// if(type == MEDIA_TYPE_VIDEO) {
// mediaFile = new File(mediaStorageDir.getPath() + File.separator +
// "VID_"+ timeStamp + ".mp4");
// } else {
// return null;
// }
//
// return Uri.fromFile(mediaFile);
// }
//
// return null;
// }
private class UploadBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(REQUEST_AUTHORIZATION_INTENT)) {
Log.d(TAG, "Request auth received - executing the intent");
Intent toRun = intent
.getParcelableExtra(REQUEST_AUTHORIZATION_INTENT_PARAM);
startActivityForResult(toRun, REQUEST_AUTHORIZATION);
}
}
}
}
================================================
FILE: app/src/main/java/com/google/ytdl/PlayActivity.java
================================================
package com.google.ytdl;
import android.app.Activity;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.google.android.youtube.player.YouTubeInitializationResult;
import com.google.android.youtube.player.YouTubePlayer;
import com.google.android.youtube.player.YouTubePlayer.OnFullscreenListener;
import com.google.android.youtube.player.YouTubePlayer.PlayerStateChangeListener;
import com.google.android.youtube.player.YouTubePlayerFragment;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.ytdl.util.VideoData;
/*
* Copyright (c) 2013 Google Inc.
*
* 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.
*/
/**
* @author Ibrahim Ulukaya <ulukaya@google.com>
* <p/>
* Main fragment showing YouTube Direct Lite upload options and having
* YT Android Player.
*/
public class PlayActivity extends Activity implements
PlayerStateChangeListener, OnFullscreenListener {
private static final String YOUTUBE_FRAGMENT_TAG = "youtube";
final HttpTransport transport = AndroidHttp.newCompatibleTransport();
final JsonFactory jsonFactory = new GsonFactory();
GoogleAccountCredential credential;
private YouTubePlayer mYouTubePlayer;
private boolean mIsFullScreen = false;
private Intent intent;
public PlayActivity() {
}
@Override
public void onStart() {
super.onStart();
}
@Override
public void onStop() {
super.onStop();
}
public void directLite(View view) {
this.setResult(RESULT_OK, intent);
finish();
}
public void panToVideo(final String youtubeId) {
popPlayerFromBackStack();
YouTubePlayerFragment playerFragment = YouTubePlayerFragment
.newInstance();
getFragmentManager()
.beginTransaction()
.replace(R.id.detail_container, playerFragment,
YOUTUBE_FRAGMENT_TAG)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.addToBackStack(null).commit();
playerFragment.initialize(Auth.KEY,
new YouTubePlayer.OnInitializedListener() {
@Override
public void onInitializationSuccess(
YouTubePlayer.Provider provider,
YouTubePlayer youTubePlayer, boolean b) {
youTubePlayer.loadVideo(youtubeId);
mYouTubePlayer = youTubePlayer;
youTubePlayer
.setPlayerStateChangeListener(PlayActivity.this);
youTubePlayer
.setOnFullscreenListener(PlayActivity.this);
}
@Override
public void onInitializationFailure(
YouTubePlayer.Provider provider,
YouTubeInitializationResult result) {
showErrorToast(result.toString());
}
});
}
public boolean popPlayerFromBackStack() {
if (mIsFullScreen) {
mYouTubePlayer.setFullscreen(false);
return false;
}
if (getFragmentManager().findFragmentByTag(YOUTUBE_FRAGMENT_TAG) != null) {
getFragmentManager().popBackStack();
return false;
}
return true;
}
@Override
public void onAdStarted() {
}
@Override
public void onError(YouTubePlayer.ErrorReason errorReason) {
showErrorToast(errorReason.toString());
}
private void showErrorToast(String message) {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT)
.show();
}
@Override
public void onLoaded(String arg0) {
}
@Override
public void onLoading() {
}
@Override
public void onVideoEnded() {
// popPlayerFromBackStack();
}
@Override
public void onVideoStarted() {
}
@Override
public void onFullscreen(boolean fullScreen) {
mIsFullScreen = fullScreen;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.activity_play);
intent = getIntent();
Button submitButton = (Button) findViewById(R.id.submit_button);
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
submitButton.setVisibility(View.GONE);
setTitle(R.string.playing_uploaded_video);
}
String youtubeId = intent.getStringExtra(MainActivity.YOUTUBE_ID);
panToVideo(youtubeId);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.play, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
super.onBackPressed();
NavUtils.navigateUpFromSameTask(this);
}
public interface Callbacks {
public void onVideoSelected(VideoData video);
public void onResume();
}
}
================================================
FILE: app/src/main/java/com/google/ytdl/ResumableUpload.java
================================================
/*
* Copyright (c) 2013 Google Inc.
*
* 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.google.ytdl;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.provider.MediaStore.Video.Thumbnails;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import com.google.api.client.googleapis.media.MediaHttpUploader;
import com.google.api.client.googleapis.media.MediaHttpUploaderProgressListener;
import com.google.api.client.http.InputStreamContent;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.model.Video;
import com.google.api.services.youtube.model.VideoListResponse;
import com.google.api.services.youtube.model.VideoSnippet;
import com.google.api.services.youtube.model.VideoStatus;
import com.google.ytdl.util.Upload;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
/**
* @author Ibrahim Ulukaya <ulukaya@google.com>
* <p/>
* YouTube Resumable Upload controller class.
*/
public class ResumableUpload {
/**
* Assigned to the upload
*/
public static final String[] DEFAULT_KEYWORDS = {"MultiSquash", "Game"};
/**
* Indicates that the video is fully processed, see https://www.googleapis.com/discovery/v1/apis/youtube/v3/rpc
*/
private static final String SUCCEEDED = "succeeded";
private static final String TAG = "UploadingActivity";
private static int UPLOAD_NOTIFICATION_ID = 1001;
private static int PLAYBACK_NOTIFICATION_ID = 1002;
/*
* Global instance of the format used for the video being uploaded (MIME type).
*/
private static String VIDEO_FILE_FORMAT = "video/*";
/**
* Uploads user selected video in the project folder to the user's YouTube account using OAuth2
* for authentication.
*/
public static String upload(YouTube youtube, final InputStream fileInputStream,
final long fileSize, final Uri mFileUri, final String path, final Context context) {
final NotificationManager notifyManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
Intent notificationIntent = new Intent(context, ReviewActivity.class);
notificationIntent.setData(mFileUri);
notificationIntent.setAction(Intent.ACTION_VIEW);
Bitmap thumbnail = ThumbnailUtils.createVideoThumbnail(path, Thumbnails.MICRO_KIND);
PendingIntent contentIntent = PendingIntent.getActivity(context,
0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
builder.setContentTitle(context.getString(R.string.youtube_upload))
.setContentText(context.getString(R.string.youtube_upload_started))
.setSmallIcon(R.drawable.ic_stat_device_access_video).setContentIntent(contentIntent).setStyle(new NotificationCompat.BigPictureStyle().bigPicture(thumbnail));
notifyManager.notify(UPLOAD_NOTIFICATION_ID, builder.build());
String videoId = null;
try {
// Add extra information to the video before uploading.
Video videoObjectDefiningMetadata = new Video();
/*
* Set the video to public, so it is available to everyone (what most people want). This is
* actually the default, but I wanted you to see what it looked like in case you need to set
* it to "unlisted" or "private" via API.
*/
VideoStatus status = new VideoStatus();
status.setPrivacyStatus("public");
videoObjectDefiningMetadata.setStatus(status);
// We set a majority of the metadata with the VideoSnippet object.
VideoSnippet snippet = new VideoSnippet();
/*
* The Calendar instance is used to create a unique name and description for test purposes, so
* you can see multiple files being uploaded. You will want to remove this from your project
* and use your own standard names.
*/
Calendar cal = Calendar.getInstance();
snippet.setTitle("Test Upload via Java on " + cal.getTime());
snippet.setDescription("Video uploaded via YouTube Data API V3 using the Java library "
+ "on " + cal.getTime());
// Set your keywords.
snippet.setTags(Arrays.asList(Constants.DEFAULT_KEYWORD, Upload.generateKeywordFromPlaylistId(Constants.UPLOAD_PLAYLIST)));
// Set completed snippet to the video object.
videoObjectDefiningMetadata.setSnippet(snippet);
InputStreamContent mediaContent =
new InputStreamContent(VIDEO_FILE_FORMAT, new BufferedInputStream(fileInputStream));
mediaContent.setLength(fileSize);
/*
* The upload command includes: 1. Information we want returned after file is successfully
* uploaded. 2. Metadata we want associated with the uploaded video. 3. Video file itself.
*/
YouTube.Videos.Insert videoInsert =
youtube.videos().insert("snippet,statistics,status", videoObjectDefiningMetadata,
mediaContent);
// Set the upload type and add event listener.
MediaHttpUploader uploader = videoInsert.getMediaHttpUploader();
/*
* Sets whether direct media upload is enabled or disabled. True = whole media content is
* uploaded in a single request. False (default) = resumable media upload protocol to upload
* in data chunks.
*/
uploader.setDirectUploadEnabled(false);
MediaHttpUploaderProgressListener progressListener = new MediaHttpUploaderProgressListener() {
public void progressChanged(MediaHttpUploader uploader) throws IOException {
switch (uploader.getUploadState()) {
case INITIATION_STARTED:
builder.setContentText(context.getString(R.string.initiation_started)).setProgress((int) fileSize,
(int) uploader.getNumBytesUploaded(), false);
notifyManager.notify(UPLOAD_NOTIFICATION_ID, builder.build());
break;
case INITIATION_COMPLETE:
builder.setContentText(context.getString(R.string.initiation_completed)).setProgress((int) fileSize,
(int) uploader.getNumBytesUploaded(), false);
notifyManager.notify(UPLOAD_NOTIFICATION_ID, builder.build());
break;
case MEDIA_IN_PROGRESS:
builder
.setContentTitle(context.getString(R.string.youtube_upload) +
(int) (uploader.getProgress() * 100) + "%")
.setContentText(context.getString(R.string.upload_in_progress))
.setProgress((int) fileSize, (int) uploader.getNumBytesUploaded(), false);
notifyManager.notify(UPLOAD_NOTIFICATION_ID, builder.build());
break;
case MEDIA_COMPLETE:
builder.setContentTitle(context.getString(R.string.yt_upload_completed))
.setContentText(context.getString(R.string.upload_completed))
// Removes the progress bar
.setProgress(0, 0, false);
notifyManager.notify(UPLOAD_NOTIFICATION_ID, builder.build());
case NOT_STARTED:
Log.d(this.getClass().getSimpleName(), context.getString(R.string.upload_not_started));
break;
}
}
};
uploader.setProgressListener(progressListener);
// Execute upload.
Video returnedVideo = videoInsert.execute();
Log.d(TAG, "Video upload completed");
videoId = returnedVideo.getId();
Log.d(TAG, String.format("videoId = [%s]", videoId));
} catch (final GooglePlayServicesAvailabilityIOException availabilityException) {
Log.e(TAG, "GooglePlayServicesAvailabilityIOException", availabilityException);
notifyFailedUpload(context, context.getString(R.string.cant_access_play), notifyManager, builder);
} catch (UserRecoverableAuthIOException userRecoverableException) {
Log.i(TAG, String.format("UserRecoverableAuthIOException: %s",
userRecoverableException.getMessage()));
requestAuth(context, userRecoverableException);
} catch (IOException e) {
Log.e(TAG, "IOException", e);
notifyFailedUpload(context, context.getString(R.string.please_try_again), notifyManager, builder);
}
return videoId;
}
private static void requestAuth(Context context,
UserRecoverableAuthIOException userRecoverableException) {
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(context);
Intent authIntent = userRecoverableException.getIntent();
Intent runReqAuthIntent = new Intent(MainActivity.REQUEST_AUTHORIZATION_INTENT);
runReqAuthIntent.putExtra(MainActivity.REQUEST_AUTHORIZATION_INTENT_PARAM, authIntent);
manager.sendBroadcast(runReqAuthIntent);
Log.d(TAG, String.format("Sent broadcast %s", MainActivity.REQUEST_AUTHORIZATION_INTENT));
}
private static void notifyFailedUpload(Context context, String message, NotificationManager notifyManager,
NotificationCompat.Builder builder) {
builder.setContentTitle(context.getString(R.string.yt_upload_failed))
.setContentText(message);
notifyManager.notify(UPLOAD_NOTIFICATION_ID, builder.build());
Log.e(ResumableUpload.class.getSimpleName(), message);
}
public static void showSelectableNotification(String videoId, Context context) {
Log.d(TAG, String.format("Posting selectable notification for video ID [%s]", videoId));
final NotificationManager notifyManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
Intent notificationIntent = new Intent(context, PlayActivity.class);
notificationIntent.putExtra(MainActivity.YOUTUBE_ID, videoId);
notificationIntent.setAction(Intent.ACTION_VIEW);
URL url;
try {
url = new URL("https://i1.ytimg.com/vi/" + videoId + "/mqdefault.jpg");
Bitmap thumbnail = BitmapFactory.decodeStream(url.openConnection().getInputStream());
PendingIntent contentIntent = PendingIntent.getActivity(context,
0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
builder.setContentTitle(context.getString(R.string.watch_your_video))
.setContentText(context.getString(R.string.see_the_newly_uploaded_video)).setContentIntent(contentIntent).setSmallIcon(R.drawable.ic_stat_device_access_video).setStyle(new NotificationCompat.BigPictureStyle().bigPicture(thumbnail));
notifyManager.notify(PLAYBACK_NOTIFICATION_ID, builder.build());
Log.d(TAG, String.format("Selectable notification for video ID [%s] posted", videoId));
} catch (MalformedURLException e) {
Log.e(TAG, e.getMessage());
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
}
/**
* @return url of thumbnail if the video is fully processed
*/
public static boolean checkIfProcessed(String videoId, YouTube youtube) {
try {
YouTube.Videos.List list = youtube.videos().list("processingDetails");
list.setId(videoId);
VideoListResponse listResponse = list.execute();
List<Video> videos = listResponse.getItems();
if (videos.size() == 1) {
Video video = videos.get(0);
String status = video.getProcessingDetails().getProcessingStatus();
Log.e(TAG, String.format("Processing status of [%s] is [%s]", videoId, status));
if (status.equals(SUCCEEDED)) {
return true;
}
} else {
// can't find the video
Log.e(TAG, String.format("Can't find video with ID [%s]", videoId));
return false;
}
} catch (IOException e) {
Log.e(TAG, "Error fetching video metadata", e);
}
return false;
}
}
================================================
FILE: app/src/main/java/com/google/ytdl/ReviewActivity.java
================================================
package com.google.ytdl;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;
public class ReviewActivity extends Activity {
VideoView mVideoView;
MediaController mc;
private String mChosenAccountName;
private Uri mFileUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.activity_review);
Button uploadButton = (Button) findViewById(R.id.upload_button);
Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
uploadButton.setVisibility(View.GONE);
setTitle(R.string.playing_the_video_in_upload_progress);
}
mFileUri = intent.getData();
loadAccount();
reviewVideo(mFileUri);
}
private void reviewVideo(Uri mFileUri) {
try {
mVideoView = (VideoView) findViewById(R.id.videoView);
mc = new MediaController(this);
mVideoView.setMediaController(mc);
mVideoView.setVideoURI(mFileUri);
mc.show();
mVideoView.start();
} catch (Exception e) {
Log.e(this.getLocalClassName(), e.toString());
}
}
private void loadAccount() {
SharedPreferences sp = PreferenceManager
.getDefaultSharedPreferences(this);
mChosenAccountName = sp.getString(MainActivity.ACCOUNT_KEY, null);
invalidateOptionsMenu();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.review, menu);
return true;
}
public void uploadVideo(View view) {
if (mChosenAccountName == null) {
return;
}
// if a video is picked or recorded.
if (mFileUri != null) {
Intent uploadIntent = new Intent(this, UploadService.class);
uploadIntent.setData(mFileUri);
uploadIntent.putExtra(MainActivity.ACCOUNT_KEY, mChosenAccountName);
startService(uploadIntent);
Toast.makeText(this, R.string.youtube_upload_started,
Toast.LENGTH_LONG).show();
// Go back to MainActivity after upload
finish();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}
================================================
FILE: app/src/main/java/com/google/ytdl/UploadService.java
================================================
/*
* Copyright (c) 2013 Google Inc.
*
* 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.google.ytdl;
import android.app.IntentService;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.services.youtube.YouTube;
import com.google.common.collect.Lists;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* @author Ibrahim Ulukaya <ulukaya@google.com>
* <p/>
* Intent service to handle uploads.
*/
public class UploadService extends IntentService {
/**
* defines how long we'll wait for a video to finish processing
*/
private static final int PROCESSING_TIMEOUT_SEC = 60 * 20; // 20 minutes
/**
* controls how often to poll for video processing status
*/
private static final int PROCESSING_POLL_INTERVAL_SEC = 60;
/**
* how long to wait before re-trying the upload
*/
private static final int UPLOAD_REATTEMPT_DELAY_SEC = 60;
/**
* max number of retry attempts
*/
private static final int MAX_RETRY = 3;
private static final String TAG = "UploadService";
/**
* processing start time
*/
private static long mStartTime;
final HttpTransport transport = AndroidHttp.newCompatibleTransport();
final JsonFactory jsonFactory = new GsonFactory();
GoogleAccountCredential credential;
/**
* tracks the number of upload attempts
*/
private int mUploadAttemptCount;
public UploadService() {
super("YTUploadService");
}
private static void zzz(int duration) throws InterruptedException {
Log.d(TAG, String.format("Sleeping for [%d] ms ...", duration));
Thread.sleep(duration);
Log.d(TAG, String.format("Sleeping for [%d] ms ... done", duration));
}
private static boolean timeoutExpired(long startTime, int timeoutSeconds) {
long currTime = System.currentTimeMillis();
long elapsed = currTime - startTime;
if (elapsed >= timeoutSeconds * 1000) {
return true;
} else {
return false;
}
}
@Override
protected void onHandleIntent(Intent intent) {
Uri fileUri = intent.getData();
String chosenAccountName = intent.getStringExtra(MainActivity.ACCOUNT_KEY);
credential =
GoogleAccountCredential.usingOAuth2(getApplicationContext(), Lists.newArrayList(Auth.SCOPES));
credential.setSelectedAccountName(chosenAccountName);
credential.setBackOff(new ExponentialBackOff());
String appName = getResources().getString(R.string.app_name);
final YouTube youtube =
new YouTube.Builder(transport, jsonFactory, credential).setApplicationName(
appName).build();
try {
tryUploadAndShowSelectableNotification(fileUri, youtube);
} catch (InterruptedException e) {
// ignore
}
}
private void tryUploadAndShowSelectableNotification(final Uri fileUri, final YouTube youtube) throws InterruptedException {
while (true) {
Log.i(TAG, String.format("Uploading [%s] to YouTube", fileUri.toString()));
String videoId = tryUpload(fileUri, youtube);
if (videoId != null) {
Log.i(TAG, String.format("Uploaded video with ID: %s", videoId));
tryShowSelectableNotification(videoId, youtube);
return;
} else {
Log.e(TAG, String.format("Failed to upload %s", fileUri.toString()));
if (mUploadAttemptCount++ < MAX_RETRY) {
Log.i(TAG, String.format("Will retry to upload the video ([%d] out of [%d] reattempts)",
mUploadAttemptCount, MAX_RETRY));
zzz(UPLOAD_REATTEMPT_DELAY_SEC * 1000);
} else {
Log.e(TAG, String.format("Giving up on trying to upload %s after %d attempts",
fileUri.toString(), mUploadAttemptCount));
return;
}
}
}
}
private void tryShowSelectableNotification(final String videoId, final YouTube youtube)
throws InterruptedException {
mStartTime = System.currentTimeMillis();
boolean processed = false;
while (!processed) {
processed = ResumableUpload.checkIfProcessed(videoId, youtube);
if (!processed) {
// wait a while
Log.d(TAG, String.format("Video [%s] is not processed yet, will retry after [%d] seconds",
videoId, PROCESSING_POLL_INTERVAL_SEC));
if (!timeoutExpired(mStartTime, PROCESSING_TIMEOUT_SEC)) {
zzz(PROCESSING_POLL_INTERVAL_SEC * 1000);
} else {
Log.d(TAG, String.format("Bailing out polling for processing status after [%d] seconds",
PROCESSING_TIMEOUT_SEC));
return;
}
} else {
ResumableUpload.showSelectableNotification(videoId, getApplicationContext());
return;
}
}
}
private String tryUpload(Uri mFileUri, YouTube youtube) {
long fileSize;
InputStream fileInputStream = null;
String videoId = null;
try {
fileSize = getContentResolver().openFileDescriptor(mFileUri, "r").getStatSize();
fileInputStream = getContentResolver().openInputStream(mFileUri);
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(mFileUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
videoId = ResumableUpload.upload(youtube, fileInputStream, fileSize, mFileUri, cursor.getString(column_index), getApplicationContext());
} catch (FileNotFoundException e) {
Log.e(getApplicationContext().toString(), e.getMessage());
} finally {
try {
fileInputStream.close();
} catch (IOException e) {
// ignore
}
}
return videoId;
}
}
================================================
FILE: app/src/main/java/com/google/ytdl/UploadsListFragment.java
================================================
/*
* Copyright (c) 2013 Google Inc.
*
* 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.google.ytdl;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.IntentSender;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.plus.Plus;
import com.google.android.gms.plus.PlusOneButton;
import com.google.android.gms.plus.model.people.Person;
import com.google.ytdl.util.VideoData;
import java.util.List;
/**
* @author Ibrahim Ulukaya <ulukaya@google.com>
* <p/>
* Left side fragment showing user's uploaded YouTube videos.
*/
public class UploadsListFragment extends Fragment implements ConnectionCallbacks,
OnConnectionFailedListener {
private static final String TAG = UploadsListFragment.class.getName();
private static Context mContext;
private Callbacks mCallbacks;
private GoogleApiClient mGoogleApiClient;
private GridView mGridView;
private ImageLoader mImageLoader;
public UploadsListFragment() {
}
@SuppressLint("ValidFragment")
public UploadsListFragment(Context context) {
mContext = context;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGoogleApiClient = new GoogleApiClient.Builder(mContext)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API)
.addScope(Plus.SCOPE_PLUS_PROFILE)
.build();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View listView = inflater.inflate(R.layout.list_fragment, container, false);
mGridView = (GridView) listView.findViewById(R.id.grid_view);
TextView emptyView = (TextView) listView.findViewById(android.R.id.empty);
mGridView.setEmptyView(emptyView);
return listView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setProfileInfo();
}
public void setVideos(List<VideoData> videos) {
if (!isAdded()) {
return;
}
mGridView.setAdapter(new UploadedVideoAdapter(videos));
}
public void setProfileInfo() {
//not sure if mGoogleapiClient.isConnect is appropriate...
if (!mGoogleApiClient.isConnected() || Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) == null) {
((ImageView) getView().findViewById(R.id.avatar))
.setImageDrawable(null);
((TextView) getView().findViewById(R.id.display_name))
.setText(R.string.not_signed_in);
} else {
Person currentPerson = Plus.PeopleApi.getCurrentPerson(mGoogleApiClient);
if (currentPerson.hasImage()) {
// Set the URL of the image that should be loaded into this view, and
// specify the ImageLoader that will be used to make the request.
((NetworkImageView) getView().findViewById(R.id.avatar)).setImageUrl(currentPerson.getImage().getUrl(), mImageLoader);
}
if (currentPerson.hasDisplayName()) {
((TextView) getView().findViewById(R.id.display_name))
.setText(currentPerson.getDisplayName());
}
}
}
@Override
public void onResume() {
super.onResume();
mGoogleApiClient.connect();
}
@Override
public void onPause() {
super.onPause();
mGoogleApiClient.disconnect();
}
@Override
public void onConnected(Bundle bundle) {
if (mGridView.getAdapter() != null) {
((UploadedVideoAdapter) mGridView.getAdapter()).notifyDataSetChanged();
}
setProfileInfo();
mCallbacks.onConnected(Plus.AccountApi.getAccountName(mGoogleApiClient));
}
@Override
public void onConnectionSuspended(int i) {
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if (connectionResult.hasResolution()) {
Toast.makeText(getActivity(),
R.string.connection_to_google_play_failed, Toast.LENGTH_SHORT)
.show();
Log.e(TAG,
String.format(
"Connection to Play Services Failed, error: %d, reason: %s",
connectionResult.getErrorCode(),
connectionResult.toString()));
try {
connectionResult.startResolutionForResult(getActivity(), 0);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, e.toString(), e);
}
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks)) {
throw new ClassCastException("Activity must implement callbacks.");
}
mCallbacks = (Callbacks) activity;
mImageLoader = mCallbacks.onGetImageLoader();
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
mImageLoader = null;
}
public interface Callbacks {
public ImageLoader onGetImageLoader();
public void onVideoSelected(VideoData video);
public void onConnected(String connectedAccountName);
}
private class UploadedVideoAdapter extends BaseAdapter {
private List<VideoData> mVideos;
private UploadedVideoAdapter(List<VideoData> videos) {
mVideos = videos;
}
@Override
public int getCount() {
return mVideos.size();
}
@Override
public Object getItem(int i) {
return mVideos.get(i);
}
@Override
public long getItemId(int i) {
return mVideos.get(i).getYouTubeId().hashCode();
}
@Override
public View getView(final int position, View convertView,
ViewGroup container) {
if (convertView == null) {
convertView = LayoutInflater.from(getActivity()).inflate(
R.layout.list_item, container, false);
}
VideoData video = mVideos.get(position);
((TextView) convertView.findViewById(android.R.id.text1))
.setText(video.getTitle());
((NetworkImageView) convertView.findViewById(R.id.thumbnail)).setImageUrl(video.getThumbUri(), mImageLoader);
if (mGoogleApiClient.isConnected()) {
((PlusOneButton) convertView.findViewById(R.id.plus_button))
.initialize(video.getWatchUri(), null);
}
convertView.findViewById(R.id.main_target).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
mCallbacks.onVideoSelected(mVideos.get(position));
}
});
return convertView;
}
}
}
================================================
FILE: app/src/main/java/com/google/ytdl/util/LruBitmapCache.java
================================================
/*
* Copyright (c) 2015 Google Inc.
*
* 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.google.ytdl.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.util.DisplayMetrics;
import com.android.volley.toolbox.ImageLoader.ImageCache;
public class LruBitmapCache extends LruCache<String, Bitmap>
implements ImageCache {
public LruBitmapCache(int maxSize) {
super(maxSize);
}
public LruBitmapCache(Context ctx) {
this(getCacheSize(ctx));
}
// Returns a cache size equal to approximately three screens worth of images.
public static int getCacheSize(Context ctx) {
final DisplayMetrics displayMetrics = ctx.getResources().
getDisplayMetrics();
final int screenWidth = displayMetrics.widthPixels;
final int screenHeight = displayMetrics.heightPixels;
// 4 bytes per pixel
final int screenBytes = screenWidth * screenHeight * 4;
return screenBytes * 3;
}
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
public Bitmap getBitmap(String url) {
return get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
}
================================================
FILE: app/src/main/java/com/google/ytdl/util/NetworkSingleton.java
================================================
/*
* Copyright (c) 2015 Google Inc.
*
* 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.google.ytdl.util;
import android.content.Context;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;
public class NetworkSingleton {
private static NetworkSingleton mInstance;
private static Context mCtx;
private RequestQueue mRequestQueue;
private ImageLoader mImageLoader;
private NetworkSingleton(Context context) {
mCtx = context;
mRequestQueue = getRequestQueue();
mImageLoader = new ImageLoader(mRequestQueue,
new LruBitmapCache(context));
}
public static synchronized NetworkSingleton getInstance(Context context) {
if (mInstance == null) {
mInstance = new NetworkSingleton(context);
}
return mInstance;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
// getApplicationContext() is key, it keeps you from leaking the
// Activity or BroadcastReceiver if someone passes one in.
mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
}
return mRequestQueue;
}
public <T> void addToRequestQueue(Request<T> req) {
getRequestQueue().add(req);
}
public ImageLoader getImageLoader() {
return mImageLoader;
}
}
================================================
FILE: app/src/main/java/com/google/ytdl/util/Upload.java
================================================
/*
* Copyright (c) 2013 Google Inc.
*
* 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.google.ytdl.util;
import com.google.ytdl.Constants;
public class Upload {
public static String generateKeywordFromPlaylistId(String playlistId) {
if (playlistId == null) playlistId = "";
if (playlistId.indexOf("PL") == 0) {
playlistId = playlistId.substring(2);
}
playlistId = playlistId.replaceAll("\\W", "");
String keyword = Constants.DEFAULT_KEYWORD.concat(playlistId);
if (keyword.length() > Constants.MAX_KEYWORD_LENGTH) {
keyword = keyword.substring(0, Constants.MAX_KEYWORD_LENGTH);
}
return keyword;
}
}
================================================
FILE: app/src/main/java/com/google/ytdl/util/Utils.java
================================================
/* Copyright (c) 2013 Google Inc.
*
* 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.google.ytdl.util;
import android.app.Activity;
import android.content.res.Resources;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;
import com.google.android.gms.auth.GoogleAuthException;
import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.ytdl.R;
/**
* Class containing some static utility methods.
*/
public class Utils {
private Utils() {
}
public static boolean hasFroyo() {
// Can use static final constants like FROYO, declared in later versions
// of the OS since they are inlined at compile time. This is guaranteed behavior.
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
}
public static boolean hasGingerbread() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
}
public static boolean hasHoneycomb() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
}
public static boolean hasHoneycombMR1() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1;
}
public static boolean hasJellyBean() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
}
/**
* Logs the given throwable and shows an error alert dialog with its message.
*
* @param activity activity
* @param tag log tag to use
* @param t throwable to log and show
*/
public static void logAndShow(Activity activity, String tag, Throwable t) {
Log.e(tag, "Error", t);
String message = t.getMessage();
if (t instanceof GoogleJsonResponseException) {
GoogleJsonError details = ((GoogleJsonResponseException) t).getDetails();
if (details != null) {
message = details.getMessage();
}
} else if (t.getCause() instanceof GoogleAuthException) {
message = ((GoogleAuthException) t.getCause()).getMessage();
}
showError(activity, message);
}
/**
* Logs the given message and shows an error alert dialog with it.
*
* @param activity activity
* @param tag log tag to use
* @param message message to log and show or {@code null} for none
*/
public static void logAndShowError(Activity activity, String tag, String message) {
String errorMessage = getErrorMessage(activity, message);
Log.e(tag, errorMessage);
showErrorInternal(activity, errorMessage);
}
/**
* Shows an error alert dialog with the given message.
*
* @param activity activity
* @param message message to show or {@code null} for none
*/
public static void showError(Activity activity, String message) {
String errorMessage = getErrorMessage(activity, message);
showErrorInternal(activity, errorMessage);
}
private static void showErrorInternal(final Activity activity, final String errorMessage) {
activity.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(activity, errorMessage, Toast.LENGTH_LONG).show();
}
});
}
private static String getErrorMessage(Activity activity, String message) {
Resources resources = activity.getResources();
if (message == null) {
return resources.getString(R.string.error);
}
return resources.getString(R.string.error_format, message);
}
}
================================================
FILE: app/src/main/java/com/google/ytdl/util/VideoData.java
================================================
/*
* Copyright (c) 2013 Google Inc.
*
* 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.google.ytdl.util;
import com.google.api.services.youtube.model.Video;
import com.google.api.services.youtube.model.VideoSnippet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author Ibrahim Ulukaya <ulukaya@google.com>
* <p/>
* Helper class to handle YouTube videos.
*/
public class VideoData {
private Video mVideo;
public Video getVideo() {
return mVideo;
}
public void setVideo(Video video) {
mVideo = video;
}
public String getYouTubeId() {
return mVideo.getId();
}
public String getTitle() {
return mVideo.getSnippet().getTitle();
}
public VideoSnippet addTags(Collection<? extends String> tags) {
VideoSnippet mSnippet = mVideo.getSnippet();
List<String> mTags = mSnippet.getTags();
if (mTags == null) {
mTags = new ArrayList<String>(2);
}
mTags.addAll(tags);
return mSnippet;
}
public String getThumbUri() {
return mVideo.getSnippet().getThumbnails().getDefault().getUrl();
}
public String getWatchUri() {
return "http://www.youtube.com/watch?v=" + getYouTubeId();
}
}
================================================
FILE: app/src/main/res/drawable/list_divider_horizontal_inset.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2013 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.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/list_divider_holo_dark"
android:insetLeft="16dp"
android:insetRight="16dp" />
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="?android:dividerHorizontal"
android:horizontalSpacing="10dp"
android:orientation="vertical"
android:showDividers="middle"
android:verticalSpacing="10dp">
<fragment
android:id="@+id/list_fragment"
android:name="com.google.ytdl.UploadsListFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
tools:layout="@layout/list_fragment" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#eee"
android:divider="?android:dividerVertical"
android:showDividers="middle">
<Button
android:id="@+id/pick_button"
style="?android:attr/borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableTop="@drawable/ic_content_picture"
android:ellipsize="end"
android:fontFamily="sans-serif-condensed"
android:hint="@string/button_pick"
android:maxLines="2"
android:onClick="pickFile"
android:padding="8dp"
android:textAppearance="?android:textAppearanceLarge"
android:textStyle="bold" />
<Button
android:id="@+id/record_button"
style="?android:attr/borderlessButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:layout_weight="1"
android:drawableTop="@drawable/ic_device_access_video"
android:ellipsize="end"
android:fontFamily="sans-serif-condensed"
android:hint="@string/button_record"
android:maxLines="2"
android:onClick="recordVideo"
android:padding="8dp"
android:textAppearance="?android:textAppearanceLarge"
android:textColorHint="#c00"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/activity_play.xml
================================================
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="?android:dividerHorizontal"
android:horizontalSpacing="10dp"
android:orientation="vertical"
android:showDividers="middle"
android:verticalSpacing="10dp"
tools:context=".PlayActivity">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<FrameLayout
android:id="@+id/detail_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#eee">
<Button
android:id="@+id/submit_button"
style="?android:attr/borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_av_upload"
android:ellipsize="end"
android:fontFamily="sans-serif-condensed"
android:hint="@string/button_submit"
android:maxLines="2"
android:onClick="directLite"
android:padding="8dp"
android:textAppearance="?android:textAppearanceLarge"
android:textColorHint="#c00"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/activity_review.xml
================================================
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="?android:dividerHorizontal"
android:horizontalSpacing="10dp"
android:orientation="vertical"
android:showDividers="middle"
android:verticalSpacing="10dp"
tools:context=".ReviewActivity">
<FrameLayout
android:id="@+id/detail_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<VideoView
android:id="@+id/videoView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#eee">
<Button
android:id="@+id/upload_button"
style="?android:attr/borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_av_upload"
android:ellipsize="end"
android:fontFamily="sans-serif-condensed"
android:hint="@string/button_upload"
android:maxLines="2"
android:onClick="uploadVideo"
android:padding="8dp"
android:textAppearance="?android:textAppearanceLarge"
android:textColorHint="#c00"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/developer_setup_required.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="fill_parent"
android:layout_height="106dp"
android:layout_margin="6dp"
android:text="@string/dev_setup_explanation"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ListView
android:id="@+id/missing_config_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="32dp"
android:layout_marginRight="32dp" />
</LinearLayout>
================================================
FILE: app/src/main/res/layout/list_fragment.xml
================================================
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="?android:dividerHorizontal"
android:dividerPadding="16dp"
android:orientation="vertical"
android:showDividers="middle">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="16dp">
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/avatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="#1000"
android:contentDescription="@string/avatar"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:fontFamily="sans-serif-light"
android:textAppearance="?android:textAppearanceLarge" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:fontFamily="sans-serif-condensed"
android:text="@string/pick_one_of_your_videos_to_submit"
android:textAppearance="?android:textAppearanceMedium"></TextView>
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<GridView
android:id="@+id/grid_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@drawable/list_divider_horizontal_inset"
android:fastScrollEnabled="true"
android:numColumns="auto_fit" />
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="16dp"
android:text="@string/no_videos_to_show_here_" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:fontFamily="sans-serif-condensed"
android:text="@string/pick_or_record_a_video_to_upload"
android:textAppearance="?android:textAppearanceMedium"></TextView>
</LinearLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/list_item.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:plus="http://schemas.android.com/apk/lib/com.google.android.gms.plus"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:minHeight="?android:listPreferredItemHeightSmall"
android:orientation="vertical"
android:paddingBottom="16dp">
<LinearLayout
android:id="@+id/main_target"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="160dp"
android:layout_marginBottom="8dp"
android:background="#1000"
android:scaleType="centerCrop" />
<TextView
android:id="@android:id/text1"
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:ellipsize="end"
android:fontFamily="sans-serif-condensed"
android:maxLines="2" />
</LinearLayout>
<com.google.android.gms.plus.PlusOneButton
android:id="@+id/plus_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
plus:annotation="inline"
plus:size="standard" />
</LinearLayout>
================================================
FILE: app/src/main/res/menu/activity_main.xml
================================================
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_accounts"
android:icon="@drawable/ic_mailboxes_accounts"
android:title="@string/accounts" />
<item
android:id="@+id/menu_refresh"
android:icon="@drawable/ic_menu_refresh"
android:title="@string/refresh" />
</menu>
================================================
FILE: app/src/main/res/menu/play.xml
================================================
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings" />
</menu>
================================================
FILE: app/src/main/res/menu/review.xml
================================================
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings" />
</menu>
================================================
FILE: app/src/main/res/values/dimens.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="thumbnail_width">80dp</dimen>
<dimen name="thumbnail_height">48dp</dimen>
<dimen name="photo_height">200dp</dimen>
<dimen name="list_item_vert_margin">8dp</dimen>
<dimen name="detail_horiz_margin">16dp</dimen>
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>
================================================
FILE: app/src/main/res/values/strings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">YouTube Direct Lite</string>
<string name="button_authorize">Authorize</string>
<string name="button_record">RECORD</string>
<string name="button_pick">GALLERY</string>
<string name="button_upload">UPLOAD TO YTDL</string>
<string name="button_submit">SUBMIT TO YTDL</string>
<string name="menu_settings">Settings</string>
<string name="title_activity_main">MainActivity</string>
<string name="title_activity_display_message">My Message</string>
<string name="google_play_not_available">Google Play Services not available</string>
<string name="youtube_upload_started">YouTube Direct Lite upload started</string>
<string name="connection_to_google_play_failed">Connection to Play Services failed.</string>
<string name="youtube_upload">YouTube upload</string>
<string name="not_signed_in">Not signed in</string>
<string name="dev_setup_explanation">If you\'re seeing this screen, congratulations! You\'ve set up the dependencies for YouTube Direct Lite correctly. You just need to set up the following for the app to work correctly:</string>
<string name="error">Error</string>
<string name="error_format">[Error] %s</string>
<string name="refresh">Refresh</string>
<string name="accounts">Accounts</string>
<string name="initiation_started">Initiation Started</string>
<string name="initiation_completed">Initiation Completed</string>
<string name="upload_in_progress">Direct Lite upload in progress</string>
<string name="yt_upload_completed">YouTube Upload Completed</string>
<string name="upload_completed">Upload completed</string>
<string name="upload_not_started">Upload Not Started!</string>
<string name="yt_upload_failed">YouTube Upload Failed</string>
<string name="reauth_required">Re-login required</string>
<string name="please_try_again">Please try again</string>
<string name="cant_access_play">Can\'t access to Google Play services</string>
<string name="title_activity_play">Click below to submit</string>
<string name="action_settings">Settings</string>
<string name="send">Click send</string>
<string name="title_activity_review">Click below to upload</string>
<string name="hello_world">Hello world!</string>
<string name="pick_one_of_your_videos_to_submit">Submit an existing YouTube video or upload a new video using the options below</string>
<string name="no_videos_to_show_here_">No videos to show here.</string>
<string name="avatar">avatar</string>
<string name="pick_or_record_a_video_to_upload">Upload an existing video from your device or record a new one</string>
<string name="see_the_newly_uploaded_video">See the newly uploaded video</string>
<string name="watch_your_video">Watch your video</string>
<string name="video_submitted_to_ytdl">Video submitted to YTDL</string>
<string name="playing_uploaded_video">Playing uploaded video</string>
<string name="playing_the_video_in_upload_progress">Playing the video in upload progress</string>
</resources>
================================================
FILE: app/src/main/res/values/styles.xml
================================================
<resources>
<!--
Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Holo.Light">
<!--
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
-->
</style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>
</resources>
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Thu Aug 20 10:24:27 EDT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Settings specified in this file will override any Gradle settings
# configured through the IDE.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
include ':app'
================================================
FILE: yt-direct-lite-android.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="yt-direct-lite-android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
gitextract_g6krwkb4/ ├── .classpath ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE-2.0.txt ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── libs/ │ │ └── YouTubeAndroidPlayerApi.jar │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── google/ │ │ └── ytdl/ │ │ ├── Auth.java │ │ ├── Constants.java │ │ ├── MainActivity.java │ │ ├── PlayActivity.java │ │ ├── ResumableUpload.java │ │ ├── ReviewActivity.java │ │ ├── UploadService.java │ │ ├── UploadsListFragment.java │ │ └── util/ │ │ ├── LruBitmapCache.java │ │ ├── NetworkSingleton.java │ │ ├── Upload.java │ │ ├── Utils.java │ │ └── VideoData.java │ └── res/ │ ├── drawable/ │ │ └── list_divider_horizontal_inset.xml │ ├── layout/ │ │ ├── activity_main.xml │ │ ├── activity_play.xml │ │ ├── activity_review.xml │ │ ├── developer_setup_required.xml │ │ ├── list_fragment.xml │ │ └── list_item.xml │ ├── menu/ │ │ ├── activity_main.xml │ │ ├── play.xml │ │ └── review.xml │ └── values/ │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── yt-direct-lite-android.iml
SYMBOL INDEX (135 symbols across 13 files)
FILE: app/src/main/java/com/google/ytdl/Auth.java
class Auth (line 20) | public class Auth {
FILE: app/src/main/java/com/google/ytdl/Constants.java
class Constants (line 22) | public class Constants {
FILE: app/src/main/java/com/google/ytdl/MainActivity.java
class MainActivity (line 79) | public class MainActivity extends Activity implements
method onCreate (line 106) | @Override
method isCorrectlyConfigured (line 153) | private boolean isCorrectlyConfigured() {
method showMissingConfigurations (line 172) | private void showMissingConfigurations() {
method onResume (line 223) | @Override
method ensureLoader (line 234) | private void ensureLoader() {
method loadAccount (line 241) | private void loadAccount() {
method saveAccount (line 248) | private void saveAccount() {
method loadData (line 254) | private void loadData() {
method onPause (line 262) | @Override
method onCreateOptionsMenu (line 274) | @Override
method onOptionsItemSelected (line 281) | @Override
method onActivityResult (line 294) | @Override
method directTag (line 357) | private void directTag(final VideoData video) {
method onSaveInstanceState (line 389) | @Override
method loadUploadedVideos (line 395) | private void loadUploadedVideos() {
method onBackPressed (line 498) | @Override
method onGetImageLoader (line 505) | @Override
method onVideoSelected (line 511) | @Override
method onConnected (line 519) | @Override
method pickFile (line 525) | public void pickFile(View view) {
method recordVideo (line 531) | public void recordVideo(View view) {
method showGooglePlayServicesAvailabilityErrorDialog (line 547) | public void showGooglePlayServicesAvailabilityErrorDialog(
method checkGooglePlayServicesAvailable (line 562) | private boolean checkGooglePlayServicesAvailable() {
method haveGooglePlayServices (line 572) | private void haveGooglePlayServices() {
method chooseAccount (line 580) | private void chooseAccount() {
class MissingConfig (line 589) | private class MissingConfig {
method MissingConfig (line 594) | public MissingConfig(String title, String body) {
class UploadBroadcastReceiver (line 637) | private class UploadBroadcastReceiver extends BroadcastReceiver {
method onReceive (line 638) | @Override
FILE: app/src/main/java/com/google/ytdl/PlayActivity.java
class PlayActivity (line 46) | public class PlayActivity extends Activity implements
method PlayActivity (line 57) | public PlayActivity() {
method onStart (line 60) | @Override
method onStop (line 66) | @Override
method directLite (line 71) | public void directLite(View view) {
method panToVideo (line 76) | public void panToVideo(final String youtubeId) {
method popPlayerFromBackStack (line 109) | public boolean popPlayerFromBackStack() {
method onAdStarted (line 121) | @Override
method onError (line 125) | @Override
method showErrorToast (line 130) | private void showErrorToast(String message) {
method onLoaded (line 135) | @Override
method onLoading (line 139) | @Override
method onVideoEnded (line 143) | @Override
method onVideoStarted (line 148) | @Override
method onFullscreen (line 152) | @Override
method onCreate (line 157) | @Override
method onCreateOptionsMenu (line 172) | @Override
method onOptionsItemSelected (line 179) | @Override
method onBackPressed (line 190) | @Override
type Callbacks (line 196) | public interface Callbacks {
method onVideoSelected (line 198) | public void onVideoSelected(VideoData video);
method onResume (line 200) | public void onResume();
FILE: app/src/main/java/com/google/ytdl/ResumableUpload.java
class ResumableUpload (line 57) | public class ResumableUpload {
method upload (line 79) | public static String upload(YouTube youtube, final InputStream fileInp...
method requestAuth (line 205) | private static void requestAuth(Context context,
method notifyFailedUpload (line 215) | private static void notifyFailedUpload(Context context, String message...
method showSelectableNotification (line 223) | public static void showSelectableNotification(String videoId, Context ...
method checkIfProcessed (line 253) | public static boolean checkIfProcessed(String videoId, YouTube youtube) {
FILE: app/src/main/java/com/google/ytdl/ReviewActivity.java
class ReviewActivity (line 19) | public class ReviewActivity extends Activity {
method onCreate (line 25) | @Override
method reviewVideo (line 42) | private void reviewVideo(Uri mFileUri) {
method loadAccount (line 55) | private void loadAccount() {
method onCreateOptionsMenu (line 62) | @Override
method uploadVideo (line 69) | public void uploadVideo(View view) {
method onOptionsItemSelected (line 86) | @Override
FILE: app/src/main/java/com/google/ytdl/UploadService.java
class UploadService (line 42) | public class UploadService extends IntentService {
method UploadService (line 74) | public UploadService() {
method zzz (line 78) | private static void zzz(int duration) throws InterruptedException {
method timeoutExpired (line 84) | private static boolean timeoutExpired(long startTime, int timeoutSecon...
method onHandleIntent (line 94) | @Override
method tryUploadAndShowSelectableNotification (line 117) | private void tryUploadAndShowSelectableNotification(final Uri fileUri,...
method tryShowSelectableNotification (line 140) | private void tryShowSelectableNotification(final String videoId, final...
method tryUpload (line 164) | private String tryUpload(Uri mFileUri, YouTube youtube) {
FILE: app/src/main/java/com/google/ytdl/UploadsListFragment.java
class UploadsListFragment (line 51) | public class UploadsListFragment extends Fragment implements ConnectionC...
method UploadsListFragment (line 61) | public UploadsListFragment() {
method UploadsListFragment (line 64) | @SuppressLint("ValidFragment")
method onCreate (line 70) | @Override
method onCreateView (line 83) | @Override
method onViewCreated (line 93) | @Override
method setVideos (line 99) | public void setVideos(List<VideoData> videos) {
method setProfileInfo (line 107) | public void setProfileInfo() {
method onResume (line 128) | @Override
method onPause (line 134) | @Override
method onConnected (line 140) | @Override
method onConnectionSuspended (line 150) | @Override
method onConnectionFailed (line 155) | @Override
method onAttach (line 175) | @Override
method onDetach (line 186) | @Override
type Callbacks (line 193) | public interface Callbacks {
method onGetImageLoader (line 194) | public ImageLoader onGetImageLoader();
method onVideoSelected (line 196) | public void onVideoSelected(VideoData video);
method onConnected (line 198) | public void onConnected(String connectedAccountName);
class UploadedVideoAdapter (line 201) | private class UploadedVideoAdapter extends BaseAdapter {
method UploadedVideoAdapter (line 204) | private UploadedVideoAdapter(List<VideoData> videos) {
method getCount (line 208) | @Override
method getItem (line 213) | @Override
method getItemId (line 218) | @Override
method getView (line 223) | @Override
FILE: app/src/main/java/com/google/ytdl/util/LruBitmapCache.java
class LruBitmapCache (line 24) | public class LruBitmapCache extends LruCache<String, Bitmap>
method LruBitmapCache (line 27) | public LruBitmapCache(int maxSize) {
method LruBitmapCache (line 31) | public LruBitmapCache(Context ctx) {
method getCacheSize (line 36) | public static int getCacheSize(Context ctx) {
method sizeOf (line 47) | @Override
method getBitmap (line 52) | @Override
method putBitmap (line 57) | @Override
FILE: app/src/main/java/com/google/ytdl/util/NetworkSingleton.java
class NetworkSingleton (line 24) | public class NetworkSingleton {
method NetworkSingleton (line 30) | private NetworkSingleton(Context context) {
method getInstance (line 38) | public static synchronized NetworkSingleton getInstance(Context contex...
method getRequestQueue (line 45) | public RequestQueue getRequestQueue() {
method addToRequestQueue (line 54) | public <T> void addToRequestQueue(Request<T> req) {
method getImageLoader (line 58) | public ImageLoader getImageLoader() {
FILE: app/src/main/java/com/google/ytdl/util/Upload.java
class Upload (line 19) | public class Upload {
method generateKeywordFromPlaylistId (line 20) | public static String generateKeywordFromPlaylistId(String playlistId) {
FILE: app/src/main/java/com/google/ytdl/util/Utils.java
class Utils (line 32) | public class Utils {
method Utils (line 33) | private Utils() {
method hasFroyo (line 36) | public static boolean hasFroyo() {
method hasGingerbread (line 42) | public static boolean hasGingerbread() {
method hasHoneycomb (line 46) | public static boolean hasHoneycomb() {
method hasHoneycombMR1 (line 50) | public static boolean hasHoneycombMR1() {
method hasJellyBean (line 54) | public static boolean hasJellyBean() {
method logAndShow (line 65) | public static void logAndShow(Activity activity, String tag, Throwable...
method logAndShowError (line 86) | public static void logAndShowError(Activity activity, String tag, Stri...
method showError (line 98) | public static void showError(Activity activity, String message) {
method showErrorInternal (line 103) | private static void showErrorInternal(final Activity activity, final S...
method getErrorMessage (line 111) | private static String getErrorMessage(Activity activity, String messag...
FILE: app/src/main/java/com/google/ytdl/util/VideoData.java
class VideoData (line 29) | public class VideoData {
method getVideo (line 32) | public Video getVideo() {
method setVideo (line 36) | public void setVideo(Video video) {
method getYouTubeId (line 40) | public String getYouTubeId() {
method getTitle (line 44) | public String getTitle() {
method addTags (line 48) | public VideoSnippet addTags(Collection<? extends String> tags) {
method getThumbUri (line 58) | public String getThumbUri() {
method getWatchUri (line 62) | public String getWatchUri() {
Condensed preview — 44 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (133K chars).
[
{
"path": ".classpath",
"chars": 466,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<classpath>\n\t<classpathentry kind=\"src\" path=\"src\"/>\n\t<classpathentry kind=\"src\" "
},
{
"path": ".gitignore",
"chars": 78,
"preview": ".gradle\n.DS_Store\nYouTubeDirectLiteforAndroid.iml\n.idea\nbuild\nlocal.properties"
},
{
"path": "CONTRIBUTING.md",
"chars": 3809,
"preview": "# How to contribute #\n\nWe'd love to accept your patches and contributions to this project. There are\na just a few small"
},
{
"path": "LICENSE-2.0.txt",
"chars": 11358,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 1539,
"preview": "YouTube Direct Lite for Android\n===========\n\nThe code is a reference implementation for an Android OS application that c"
},
{
"path": "app/.gitignore",
"chars": 14,
"preview": "/build\napp.iml"
},
{
"path": "app/build.gradle",
"chars": 1057,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdkVersion 23\n buildToolsVersion \"23.0.0\"\n\n defaultC"
},
{
"path": "app/proguard-rules.pro",
"chars": 658,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 1663,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "app/src/main/java/com/google/ytdl/Auth.java",
"chars": 965,
"preview": "/*\n * Copyright (c) 2013 Google Inc.\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not "
},
{
"path": "app/src/main/java/com/google/ytdl/Constants.java",
"chars": 1170,
"preview": "/*\n * Copyright (c) 2013 Google Inc.\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not "
},
{
"path": "app/src/main/java/com/google/ytdl/MainActivity.java",
"chars": 24830,
"preview": "/*\n * Copyright (c) 2013 Google Inc.\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not "
},
{
"path": "app/src/main/java/com/google/ytdl/PlayActivity.java",
"chars": 6585,
"preview": "package com.google.ytdl;\n\nimport android.app.Activity;\nimport android.app.FragmentTransaction;\nimport android.content.In"
},
{
"path": "app/src/main/java/com/google/ytdl/ResumableUpload.java",
"chars": 14155,
"preview": "/*\n * Copyright (c) 2013 Google Inc.\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not "
},
{
"path": "app/src/main/java/com/google/ytdl/ReviewActivity.java",
"chars": 3179,
"preview": "package com.google.ytdl;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.SharedPrefe"
},
{
"path": "app/src/main/java/com/google/ytdl/UploadService.java",
"chars": 7292,
"preview": "/*\n * Copyright (c) 2013 Google Inc.\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not "
},
{
"path": "app/src/main/java/com/google/ytdl/UploadsListFragment.java",
"chars": 8515,
"preview": "/*\n * Copyright (c) 2013 Google Inc.\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not "
},
{
"path": "app/src/main/java/com/google/ytdl/util/LruBitmapCache.java",
"chars": 1879,
"preview": "/*\n * Copyright (c) 2015 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not u"
},
{
"path": "app/src/main/java/com/google/ytdl/util/NetworkSingleton.java",
"chars": 1979,
"preview": "/*\n * Copyright (c) 2015 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not u"
},
{
"path": "app/src/main/java/com/google/ytdl/util/Upload.java",
"chars": 1217,
"preview": "/*\n * Copyright (c) 2013 Google Inc.\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not "
},
{
"path": "app/src/main/java/com/google/ytdl/util/Utils.java",
"chars": 4143,
"preview": "/* Copyright (c) 2013 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not u"
},
{
"path": "app/src/main/java/com/google/ytdl/util/VideoData.java",
"chars": 1820,
"preview": "/*\n * Copyright (c) 2013 Google Inc.\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not "
},
{
"path": "app/src/main/res/drawable/list_divider_horizontal_inset.xml",
"chars": 834,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n Copyright 2013 The Android Open Source Project\n \n Licensed under the Ap"
},
{
"path": "app/src/main/res/layout/activity_main.xml",
"chars": 2348,
"preview": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/too"
},
{
"path": "app/src/main/res/layout/activity_play.xml",
"chars": 1675,
"preview": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/too"
},
{
"path": "app/src/main/res/layout/activity_review.xml",
"chars": 1678,
"preview": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tools=\"http://schemas.android.com/too"
},
{
"path": "app/src/main/res/layout/developer_setup_required.xml",
"chars": 812,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n and"
},
{
"path": "app/src/main/res/layout/list_fragment.xml",
"chars": 3169,
"preview": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:layout_width=\"match_parent\"\n and"
},
{
"path": "app/src/main/res/layout/list_item.xml",
"chars": 1946,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/menu/activity_main.xml",
"chars": 364,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item\n android:id=\"@+id/menu_accounts\"\n "
},
{
"path": "app/src/main/res/menu/play.xml",
"chars": 254,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <item\n android:id=\"@+id/action_settings\"\n "
},
{
"path": "app/src/main/res/menu/review.xml",
"chars": 254,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <item\n android:id=\"@+id/action_settings\"\n "
},
{
"path": "app/src/main/res/values/dimens.xml",
"chars": 422,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <dimen name=\"thumbnail_width\">80dp</dimen>\n <dimen name=\"thum"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 3136,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"app_name\">YouTube Direct Lite</string>\n <string"
},
{
"path": "app/src/main/res/values/styles.xml",
"chars": 684,
"preview": "<resources>\n\n <!--\n Base application theme, dependent on API level. This theme is replaced\n by AppBaseT"
},
{
"path": "build.gradle",
"chars": 436,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n r"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 230,
"preview": "#Thu Aug 20 10:24:27 EDT 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "gradle.properties",
"chars": 853,
"preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Settings specified in this file will override any "
},
{
"path": "gradlew",
"chars": 5080,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "gradlew.bat",
"chars": 2314,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
},
{
"path": "settings.gradle",
"chars": 15,
"preview": "include ':app'\n"
},
{
"path": "yt-direct-lite-android.iml",
"chars": 953,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module external.linked.project.id=\"yt-direct-lite-android\" external.linked.proje"
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the youtube/yt-direct-lite-android GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 44 files (122.9 KB), approximately 27.7k tokens, and a symbol index with 135 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.