1
0
mirror of https://github.com/TeamNewPipe/NewPipe.git synced 2024-11-24 03:52:36 +01:00

added autio streaming & jumped to version 0.4.0

This commit is contained in:
Christian Schabesberger 2015-09-21 21:12:48 +02:00
parent 1ab5872857
commit d24c87c9c9
13 changed files with 393 additions and 64 deletions

View File

@ -37,7 +37,7 @@
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -8,8 +8,8 @@ android {
applicationId "org.schabi.newpipe"
minSdkVersion 15
targetSdkVersion 23
versionCode 2
versionName "0.3.5"
versionCode 3
versionName "0.4.0"
}
buildTypes {
release {

View File

@ -6,6 +6,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.support.v4.view.MenuItemCompat;
@ -50,6 +51,7 @@ public class ActionBarHandler {
private String webisteUrl = "";
private AppCompatActivity activity;
private VideoInfo.VideoStream[] videoStreams = null;
private VideoInfo.AudioStream audioStream = null;
private int selectedStream = -1;
private String videoTitle = "";
@ -75,18 +77,18 @@ public class ActionBarHandler {
activity.getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
}
public void setStreams(VideoInfo.VideoStream[] streams) {
this.videoStreams = streams;
public void setStreams(VideoInfo.VideoStream[] videoStreams, VideoInfo.AudioStream[] audioStreams) {
this.videoStreams = videoStreams;
selectedStream = 0;
String[] itemArray = new String[streams.length];
String[] itemArray = new String[videoStreams.length];
String defaultResolution = defaultPreferences
.getString(context.getString(R.string.defaultResolutionPreference),
context.getString(R.string.defaultResolutionListItem));
int defaultResolutionPos = 0;
for(int i = 0; i < streams.length; i++) {
itemArray[i] = streams[i].format + " " + streams[i].resolution;
if(defaultResolution.equals(streams[i].resolution)) {
for(int i = 0; i < videoStreams.length; i++) {
itemArray[i] = VideoInfo.getNameById(videoStreams[i].format) + " " + videoStreams[i].resolution;
if(defaultResolution.equals(videoStreams[i].resolution)) {
defaultResolutionPos = i;
}
}
@ -99,6 +101,27 @@ public class ActionBarHandler {
,new ForamatItemSelectListener());
ab.setSelectedNavigationItem(defaultResolutionPos);
}
// set audioStream
audioStream = null;
String preferedFormat = PreferenceManager.getDefaultSharedPreferences(context)
.getString(context.getString(R.string.defaultAudioFormatPreference), "webm");
if(preferedFormat.equals("webm")) {
for(VideoInfo.AudioStream s : audioStreams) {
if(s.format == VideoInfo.I_WEBMA) {
audioStream = s;
}
}
} else if(preferedFormat.equals("m4a")){
for(VideoInfo.AudioStream s : audioStreams) {
Log.d(TAG, VideoInfo.getMimeById(s.format) + " : " + Integer.toString(s.bandWidth));
if(s.format == VideoInfo.I_M4A &&
(audioStream == null || audioStream.bandWidth > s.bandWidth)) {
audioStream = s;
Log.d(TAG, "last choosen");
}
}
}
}
private void selectFormatItem(int i) {
@ -159,6 +182,9 @@ public class ActionBarHandler {
case R.id.action_play_with_kodi:
playWithKodi();
break;
case R.id.menu_item_play_audio:
playAudio();
break;
default:
Log.e(TAG, "Menu Item not known");
}
@ -179,7 +205,7 @@ public class ActionBarHandler {
try {
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(videoStreams[selectedStream].url),
"video/" + videoStreams[selectedStream].format);
VideoInfo.getMimeById(videoStreams[selectedStream].format));
context.startActivity(intent); // HERE !!!
} catch (Exception e) {
e.printStackTrace();
@ -216,29 +242,17 @@ public class ActionBarHandler {
public void downloadVideo() {
Log.d(TAG, "bla");
if(!videoTitle.isEmpty()) {
String suffix = "";
switch (videoStreams[selectedStream].format) {
case VideoInfo.F_WEBM:
suffix = ".webm";
break;
case VideoInfo.F_MPEG_4:
suffix = ".mp4";
break;
case VideoInfo.F_3GPP:
suffix = ".3gp";
break;
}
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(
Uri.parse(videoStreams[selectedStream].url));
request.setDestinationUri(Uri.fromFile(new File(
defaultPreferences.getString("download_path_preference", "/storage/emulated/0/NewPipe")
+ "/" + videoTitle + suffix)));
try {
dm.enqueue(request);
} catch (Exception e) {
e.printStackTrace();
}
String videoSuffix = "." + VideoInfo.getSuffixById(videoStreams[selectedStream].format);
String audioSuffix = "." + VideoInfo.getSuffixById(audioStream.format);
Bundle args = new Bundle();
args.putString(DownloadDialog.FILE_SUFFIX_VIDEO, videoSuffix);
args.putString(DownloadDialog.FILE_SUFFIX_AUDIO, audioSuffix);
args.putString(DownloadDialog.TITLE, videoTitle);
args.putString(DownloadDialog.VIDEO_URL, videoStreams[selectedStream].url);
args.putString(DownloadDialog.AUDIO_URL, audioStream.url);
DownloadDialog downloadDialog = new DownloadDialog();
downloadDialog.setArguments(args);
downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
}
}
@ -282,4 +296,34 @@ public class ActionBarHandler {
}
}
}
public void playAudio() {
Intent intent = new Intent();
try {
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(audioStream.url),
VideoInfo.getMimeById(audioStream.format));
context.startActivity(intent); // HERE !!!
} catch (Exception e) {
e.printStackTrace();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(R.string.noPlayerFound)
.setPositiveButton(R.string.installStreamPlayer, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(context.getString(R.string.fdroidVLCurl)));
context.startActivity(intent);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.i(TAG, "You unlocked a secret unicorn.");
}
});
builder.create().show();
}
}
}

View File

@ -0,0 +1,89 @@
package org.schabi.newpipe;
import android.app.Dialog;
import android.app.DownloadManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import java.io.File;
/**
* Created by Christian Schabesberger on 21.09.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* DownloadDialog.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class DownloadDialog extends DialogFragment {
private static final String TAG = DialogFragment.class.getName();
public static final String TITLE = "name";
public static final String FILE_SUFFIX_AUDIO = "file_suffix_audio";
public static final String FILE_SUFFIX_VIDEO = "file_suffix_video";
public static final String AUDIO_URL = "audio_url";
public static final String VIDEO_URL = "video_url";
Bundle arguments;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
arguments = getArguments();
super.onCreateDialog(savedInstanceState);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.downloadDialogTitle)
.setItems(R.array.downloadOptions, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Context context = getActivity();
SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String suffix = "";
String title = arguments.getString(TITLE);
String url = "";
switch(which) {
case 0: // Video
suffix = arguments.getString(FILE_SUFFIX_VIDEO);
url = arguments.getString(VIDEO_URL);
break;
case 1:
suffix = arguments.getString(FILE_SUFFIX_AUDIO);
url = arguments.getString(AUDIO_URL);
break;
default:
Log.d(TAG, "lolz");
}
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(
Uri.parse(url));
request.setDestinationUri(Uri.fromFile(new File(
defaultPreferences.getString("download_path_preference", "/storage/emulated/0/NewPipe")
+ "/" + title + suffix)));
try {
dm.enqueue(request);
} catch (Exception e) {
e.printStackTrace();
}
}
});
return builder.create();
}
}

View File

@ -21,35 +21,104 @@ package org.schabi.newpipe;
*/
import android.graphics.Bitmap;
import android.util.Log;
import java.util.Vector;
public class VideoInfo {
private static final String TAG = VideoInfo.class.toString();
// format identifier
public static final int I_MPEG_4 = 0x0;
public static final int I_3GPP = 0x1;
public static final int I_WEBM = 0x2;
public static final int I_M4A = 0x3;
public static final int I_WEBMA = 0x4;
// format name
public static final String F_MPEG_4 = "MPEG-4";
public static final String F_3GPP = "3GPP";
public static final String F_WEBM = "WebM";
public static final String F_M4A = "m4a";
public static final String F_WEBMA = "WebM";
// file suffix
public static final String C_MPEG_4 = "mp4";
public static final String C_3GPP = "3gp";
public static final String C_WEBM = "webm";
public static final String C_M4A = "m4a";
public static final String C_WEBMA = "webm";
// mimeType
public static final String M_MPEG_4 = "video/mp4";
public static final String M_3GPP = "video/3gpp";
public static final String M_WEBM = "video/webm";
public static final String M_M4A = "audio/mp4";
public static final String M_WEBMA = "audio/webm";
public static final int VIDEO_AVAILABLE = 0x00;
public static final int VIDEO_UNAVAILABLE = 0x01;
public static final int VIDEO_UNAVAILABLE_GEMA = 0x02;
public static String getNameById(int id) {
switch(id) {
case I_MPEG_4: return F_MPEG_4;
case I_3GPP: return F_3GPP;
case I_WEBM: return F_WEBM;
case I_M4A: return F_M4A;
case I_WEBMA: return F_WEBMA;
default: Log.e(TAG, "format not known: " +
Integer.toString(id) + "call the programmer he messed it up.");
}
return "";
}
public static String getSuffixById(int id) {
switch(id) {
case I_MPEG_4: return C_MPEG_4;
case I_3GPP: return C_3GPP;
case I_WEBM: return C_WEBM;
case I_M4A: return C_M4A;
case I_WEBMA: return C_WEBMA;
default: Log.e(TAG, "format not known: " +
Integer.toString(id) + "call the programmer he messed it up.");
}
return "";
}
public static String getMimeById(int id) {
switch(id) {
case I_MPEG_4: return M_MPEG_4;
case I_3GPP: return M_3GPP;
case I_WEBM: return M_WEBM;
case I_M4A: return M_M4A;
case I_WEBMA: return M_WEBMA;
default: Log.e(TAG, "format not known: " +
Integer.toString(id) + "call the programmer he messed it up.");
}
return "";
}
public static class VideoStream {
public VideoStream(String url, String format, String res) {
public VideoStream(String url, int format, String res) {
this.url = url; this.format = format; resolution = res;
}
public String url = ""; //url of the stream
public String format = "";
public int format = -1;
public String resolution = "";
}
public static class AudioStream {
public AudioStream(String url, String format) {
public AudioStream(String url, int format, int bandWidth, int samplingRate) {
this.url = url; this.format = format;
this.bandWidth = bandWidth; this.samplingRate = samplingRate;
}
public String url = "";
public String format = "";
public int format = -1;
public int bandWidth = -1;
public int samplingRate = -1;
}
public String id = "";

View File

@ -205,7 +205,7 @@ public class VideoItemDetailFragment extends Fragment {
for (int i = 0; i < streamList.length; i++) {
streamList[i] = streamsToUse.get(i);
}
ActionBarHandler.getHandler().setStreams(streamList);
ActionBarHandler.getHandler().setStreams(streamList, info.audioStreams);
}
break;
case VideoInfo.VIDEO_UNAVAILABLE_GEMA:

View File

@ -5,7 +5,9 @@ import org.schabi.newpipe.Extractor;
import org.schabi.newpipe.VideoInfo;
import android.util.Log;
import android.util.Xml;
import java.io.StringReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
@ -22,6 +24,7 @@ import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.ScriptableObject;
import org.schabi.newpipe.VideoInfoItem;
import org.xmlpull.v1.XmlPullParser;
/**
* Created by Christian Schabesberger on 06.08.15.
@ -53,22 +56,22 @@ public class YoutubeExtractor implements Extractor {
// How ever if you are heading for a list showing all itag formats lock at
// https://github.com/rg3/youtube-dl/issues/1687
public static String resolveFormat(int itag) {
public static int resolveFormat(int itag) {
switch(itag) {
// video
case 17: return VideoInfo.F_3GPP;
case 18: return VideoInfo.F_MPEG_4;
case 22: return VideoInfo.F_MPEG_4;
case 36: return VideoInfo.F_3GPP;
case 37: return VideoInfo.F_MPEG_4;
case 38: return VideoInfo.F_MPEG_4;
case 43: return VideoInfo.F_WEBM;
case 44: return VideoInfo.F_WEBM;
case 45: return VideoInfo.F_WEBM;
case 46: return VideoInfo.F_WEBM;
case 17: return VideoInfo.I_3GPP;
case 18: return VideoInfo.I_MPEG_4;
case 22: return VideoInfo.I_MPEG_4;
case 36: return VideoInfo.I_3GPP;
case 37: return VideoInfo.I_MPEG_4;
case 38: return VideoInfo.I_MPEG_4;
case 43: return VideoInfo.I_WEBM;
case 44: return VideoInfo.I_WEBM;
case 45: return VideoInfo.I_WEBM;
case 46: return VideoInfo.I_WEBM;
default:
//Log.i(TAG, "Itag " + Integer.toString(itag) + " not known or not supported.");
return null;
return -1;
}
}
@ -153,6 +156,7 @@ public class YoutubeExtractor implements Extractor {
//-------------------------------------
JSONObject playerArgs = null;
JSONObject ytAssets = null;
String dashManifest = "";
{
Pattern p = Pattern.compile("ytplayer.config\\s*=\\s*(\\{.*?\\});");
Matcher m = p.matcher(site);
@ -180,10 +184,20 @@ public class YoutubeExtractor implements Extractor {
videoInfo.duration = playerArgs.getInt("length_seconds");
videoInfo.average_rating = playerArgs.getString("avg_rating");
// View Count will be extracted from html
//videoInfo.view_count = playerArgs.getString("view_count");
dashManifest = playerArgs.getString("dashmpd");
String playerUrl = ytAssets.getString("js");
if(playerUrl.startsWith("//")) {
playerUrl = "https:" + playerUrl;
}
if(decryptoinCode.isEmpty()) {
decryptoinCode = loadDecryptioinCode(playerUrl);
}
// extract audio
videoInfo.audioStreams = parseDashManifest(dashManifest, decryptoinCode);
//------------------------------------
// extract stream url
// extract video stream url
//------------------------------------
String encoded_url_map = playerArgs.getString("url_encoded_fmt_stream_map");
Vector<VideoInfo.VideoStream> videoStreams = new Vector<>();
@ -199,17 +213,13 @@ public class YoutubeExtractor implements Extractor {
// if video has a signature: decrypt it and add it to the url
if(tags.get("s") != null) {
String playerUrl = ytAssets.getString("js");
if(playerUrl.startsWith("//")) {
playerUrl = "https:" + playerUrl;
}
if(decryptoinCode.isEmpty()) {
decryptoinCode = loadDecryptioinCode(playerUrl);
}
streamUrl = streamUrl + "&signature=" + decriptSignature(tags.get("s"), decryptoinCode);
streamUrl = streamUrl + "&signature=" + decryptSignature(tags.get("s"), decryptoinCode);
}
if(resolveFormat(itag) != null) {
if(resolveFormat(itag) != -1) {
videoStreams.add(new VideoInfo.VideoStream(
streamUrl,
resolveFormat(itag),
@ -309,6 +319,81 @@ public class YoutubeExtractor implements Extractor {
return videoInfo;
}
private VideoInfo.AudioStream[] parseDashManifest(String dashManifest, String decryptoinCode) {
if(!dashManifest.contains("/signature/")) {
String encryptedSig = "";
String decryptedSig = "";
try {
Pattern p = Pattern.compile("/s/([a-fA-F0-9\\.]+)");
Matcher m = p.matcher(dashManifest);
m.find();
encryptedSig = m.group(1);
} catch (Exception e) {
e.printStackTrace();
}
decryptedSig = decryptSignature(encryptedSig, decryptoinCode);
dashManifest = dashManifest.replace("/s/" + encryptedSig, "/signature/" + decryptedSig);
}
String dashDoc = Downloader.download(dashManifest);
Vector<VideoInfo.AudioStream> audioStreams = new Vector<>();
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new StringReader(dashDoc));
int eventType = parser.getEventType();
String tagName = "";
String currentMimeType = "";
int currentBandwidth = -1;
int currentSamplingRate = -1;
boolean currentTagIsBaseUrl = false;
while(eventType != XmlPullParser.END_DOCUMENT) {
switch(eventType) {
case XmlPullParser.START_TAG:
tagName = parser.getName();
if(tagName.equals("AdaptationSet")) {
currentMimeType = parser.getAttributeValue(XmlPullParser.NO_NAMESPACE, "mimeType");
} else if(tagName.equals("Representation") && currentMimeType.contains("audio")) {
currentBandwidth = Integer.parseInt(
parser.getAttributeValue(XmlPullParser.NO_NAMESPACE, "bandwidth"));
currentSamplingRate = Integer.parseInt(
parser.getAttributeValue(XmlPullParser.NO_NAMESPACE, "audioSamplingRate"));
} else if(tagName.equals("BaseURL")) {
currentTagIsBaseUrl = true;
}
break;
case XmlPullParser.TEXT:
if(currentTagIsBaseUrl &&
(currentMimeType.contains("audio"))) {
int format = -1;
if(currentMimeType.equals(VideoInfo.M_WEBMA)) {
format = VideoInfo.I_WEBMA;
} else if(currentMimeType.equals(VideoInfo.M_M4A)) {
format = VideoInfo.I_M4A;
}
audioStreams.add(new VideoInfo.AudioStream(parser.getText(),
format, currentBandwidth, currentSamplingRate));
}
case XmlPullParser.END_TAG:
if(tagName.equals("AdaptationSet")) {
currentMimeType = "";
} else if(tagName.equals("BaseURL")) {
currentTagIsBaseUrl = false;
}
break;
default:
}
eventType = parser.next();
}
} catch(Exception e) {
e.printStackTrace();
}
VideoInfo.AudioStream[] output = new VideoInfo.AudioStream[audioStreams.size()];
for(int i = 0; i < output.length; i++) {
output[i] = audioStreams.get(i);
}
return output;
}
private VideoInfoItem extractVideoInfoItem(Element li) {
VideoInfoItem info = new VideoInfoItem();
info.webpage_url = li.select("a[class*=\"content-link\"]").first()
@ -395,7 +480,7 @@ public class YoutubeExtractor implements Extractor {
return decryptionCode;
}
private String decriptSignature(String encryptedSig, String decryptoinCode) {
private String decryptSignature(String encryptedSig, String decryptoinCode) {
Context context = Context.enter();
context.setOptimizationLevel(-1);
Object result = null;

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

View File

@ -3,14 +3,24 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_item_play"
android:title="@string/play"
app:showAsAction="always"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_play_arrow_black"/>
<item android:id="@+id/menu_item_play_audio"
android:title="@string/playAudio"
app:showAsAction="always"
android:icon="@drawable/ic_headset_black" />
<item android:id="@+id/menu_item_share"
android:title="@string/share"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_share_black"/>
<item android:id="@+id/action_play_with_kodi"
android:title="@string/playWithKodiTitle"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_cast_black"/>
<item android:id="@+id/menu_item_openInBrowser"
app:showAsAction="never"
android:title="@string/open_in_browser" />
@ -23,8 +33,5 @@
app:showAsAction="never"
android:title="@string/settings"/>
<item android:id="@+id/action_play_with_kodi"
android:title="@string/playWithKodiTitle"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_cast_black"/>
</menu>

View File

@ -36,4 +36,13 @@
<string name="showPlayWithKodiTitle">Zeige \"Mit Kodi abspielen\" Option</string>
<string name="showPlayWithKodiSummary">Zeigt eine Option an, über die man Videos mit dem Kodi Mediacenter abspielen kann.</string>
<string name="leftHandLayoutTitle">Linkshänder freundliches Layout.</string>
<string name="playAudio">Audio</string>
<string name="defaultAudioFormatTitle">Bevorzugtes Audio Format</string>
<string name="webMAudioDescription">WebM - freies Format</string>
<string name="m4aAudioDescription">m4a - bessere Qualität</string>
<string name="downloadDialogTitle">Herunterladen</string>
<string-array name="downloadOptions">
<item>Video</item>
<item>Audio</item>
</string-array>
</resources>

View File

@ -13,4 +13,14 @@
<string name="defaultResolutionListItem">360p</string>
<string name="showPlayWidthKodiPreference">show_play_with_kodi_preference</string>
<string name="leftHandLayout">left_hand_layout</string>
<string name="defaultAudioFormatPreference">default_audio_format</string>
<string-array name="audioFormatDescriptionList">
<item>@string/webMAudioDescription</item>
<item>@string/m4aAudioDescription</item>
</string-array>
<string-array name="audioFormatList">
<item>webm</item>
<item>m4a</item>
</string-array>
<string name="defaultAudioFormat">m4a</string>
</resources>

View File

@ -36,4 +36,13 @@
<string name="showPlayWithKodiTitle">Show \"Play with Kodi\" option</string>
<string name="showPlayWithKodiSummary">Displays an option to play a video via Kodi media center.</string>
<string name="leftHandLayoutTitle">Left hand friendly layout.</string>
<string name="playAudio">Audio</string>
<string name="defaultAudioFormatTitle">Default audio format</string>
<string name="webMAudioDescription">WebM - free format</string>
<string name="m4aAudioDescription">m4a - better quality</string>
<string name="downloadDialogTitle">Download</string>
<string-array name="downloadOptions">
<item>Video</item>
<item>Audio</item>
</string-array>
</resources>

View File

@ -39,4 +39,11 @@
android:title="@string/leftHandLayoutTitle"
android:defaultValue="false" />
<ListPreference
android:key="@string/defaultAudioFormatPreference"
android:title="@string/defaultAudioFormatTitle"
android:entries="@array/audioFormatDescriptionList"
android:entryValues="@array/audioFormatList"
android:defaultValue="@string/defaultAudioFormat"/>
</PreferenceScreen>