I have a dash manifest which needs to be loaded in ExoPlayer, which is also DRM protected.
I can't seem to find any example as to how to achieve that other than using JWPlayer Android
which did not work. executeProvisionRequest or executeKeyRequest was never called on loading my content.
Is there a simpler way to do this with ExoPlayer?
PlaylistItem content = new PlaylistItem.Builder()
.file("MY_MANIFEST_FILE_PATH")
.mediaDrmCallback(new WidevineMediaDrmCallback())
.build();
mPlayerView.load(content);
public class WidevineMediaDrmCallback implements MediaDrmCallback {
#Override
public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest provisionRequest) throws Exception {
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
return Util.executePost(url, null, null);
}
#Override
public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request) throws Exception {
String url = request.getLicenseServerUrl();
if (TextUtils.isEmpty(url)) {
url = defaultUri;
}
return Util.executePost(url, request.getData(), null);
}
}
Related
I'm trying to preview (thumbnail) PDF documents that are remotely, using the Glide library from bumptech, version 4.8.0.
To achieve this, following the excellent tutorial Writing a custom ModelLoader, I've written a custom ModelLoader, a custom DataFetcher for the buildLoadData method; added the AppGlideModule, implemented ModelLoaderFactory and registered my ModelLoader.
Inside the DataFetcher I've added some logic to process the following two cases:
The content is an image. Works like a charm!
The content is a PDF document. W/Glide: Load failed for https://www.testserver.net/folder/sample.pdf with size [522x600]
class com.bumptech.glide.load.engine.GlideException: Failed to load resource
One approach has been to download the PDF file locally, and then render it (this DOES work), but it adds a considerable delay when having to download a file from a url and copy it locally; on the other hand, it doesn't take advantage of Glide's use of the cache.
Should I add another extra ModelLoader to use OkHttp3 instead of Volley (default)?
Any ideas? Thanks in advance!
public final class MyModelLoader implements ModelLoader<File, InputStream> {
private final Context context;
public MyModelLoader(Context context) {
this.context = context;
}
#NonNull
#Override
public ModelLoader.LoadData<InputStream> buildLoadData(#NonNull File model, int width, int height, #NonNull Options options) {
return new ModelLoader.LoadData<>(new ObjectKey(model), new MyDataFetcher(context, model));
}
#Override
public boolean handles(#NonNull File file) {
return true;
}
}
public class MyDataFetcher implements DataFetcher<InputStream> {
#SuppressWarnings("FieldCanBeLocal")
private final Context context;
private final File file;
private InputStream inputStream;
public MyDataFetcher(Context context, File file) {
this.context = context;
this.file = file;
}
#Override
public void loadData(#NonNull Priority priority, #NonNull DataCallback<? super InputStream> callback) {
try {
if (isPdf(file)) {
//We have a PDF document in "file" -- fail (if document is remote)
try {
//render first page of document PDF to bitmap, and pass to method 'onDataReady' as a InputStream
PdfRenderer pdfRenderer = new PdfRenderer(ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY));
PdfRenderer.Page page = pdfRenderer.openPage(0);
int width = 2048;
int height = (page.getHeight() * (width / page.getWidth()));
Bitmap pageBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
page.render(pageBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
pageBitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream);
ByteArrayInputStream stream = new ByteArrayInputStream(outputStream.toByteArray());
callback.onDataReady(stream);
} catch (IOException ignored) {}
} else {
//We have an image in "file" -- OK
FileInputStream fileInputStream = new FileInputStream(file);
callback.onDataReady(fileInputStream);
}
} catch (IOException ignored) {}
}
// checks for file content
public boolean isPdf(File f) throws IOException {
URLConnection connection = f.toURL().openConnection();
String mimeType = connection.getContentType();
return mimeType.equals("application/pdf");
}
#Override
public void cleanup() {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ignored) {}
}
}
#Override
public void cancel() {
//empty
}
#NonNull
#Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
#NonNull
#Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
}
public class MyModelLoaderFactory implements ModelLoaderFactory<File, InputStream> {
private final Context context;
public MyModelLoaderFactory(Context context) {
this.context = context;
}
#NonNull
#Override
public ModelLoader<File, InputStream> build(#NonNull MultiModelLoaderFactory multiFactory) {
return new MyModelLoader(context);
}
#Override
public void teardown() {
//empty
}
}
#GlideModule public class MyAppGlideModule extends AppGlideModule {
#Override
public void registerComponents(#NonNull Context context, #NonNull Glide glide, Registry registry) {
registry.prepend(File.class, InputStream.class, new MyModelLoaderFactory(context));
}
}
Finally, after all of the above, the call is of the form:
GlideApp.with(image.getContext()).load("resource_url").into(image);
Where "resouce_url" could be: https://www.testserver.net/folder/sample.pdf, eg.
You can show image and video thumbnails using Glide library if you want to show a pdf thumbnail you need to use the library for it e.g. Android PdfViewer. Then instead of using ImageView use PdfView and load only the first page instead of all e.g. .pages(0). That's it.
In your xml/layout
<com.github.barteksc.pdfviewer.PDFView
android:id="#+id/pdfView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
In your class
pdfView.fromBytes(bytes)
.pages(0) //show only first page
.spacing(0)
.swipeHorizontal(false)
.enableSwipe(false)
.load();
Well, I've finally found a method to solve the problem, from another perspective.
Instead of preprocessing the pdf on client-side, using a Glide ModelLoader, I've come up with an outlier but effective subterfuge: do it on server-side.
By means of the php Imagick extension, I've modified the server api so that it automatically generates a thumbnail on the server (in the "upload.php" module), same path where the pdf is saved. Thus, assuming that we have the pdf already loaded, we do the following:
// Create thumbnail, if pdf
if ($ext == 'pdf') {
$im = new imagick($base_path.$next_id["next"].".".$ext.'[0]');
$im->setImageFormat('jpg');
header('Content-Type: image/jpeg');
file_put_contents($base_path.$next_id["next"]."_thumbnail.jpg", $im);
}
(with the help of this link on using Imagick to convert pdf to jpg: How to convert pdf to a preview image in PHP).
On the other hand, when a record is deleted, the attachments that it may have associated must also be deleted, if any. This makes it necessary to also delete the thumbnail, in the same act, as shown below:
// Remove uploaded file from server
unlink($base_path.$id.".".$ext);
// If pdf, we also have to remove the thumbnail
if ($ext == 'pdf') {
unlink($base_path.$id."_thumbnail.jpg");
}
Now we have a set of files, some jpg/png and another pdf; but this is indifferent for Glide, which will only show jpg/png images, without any problem, even if they are remotely; and of course very quickly. The code on client-side is:
/* Have a pdf file, eg. "sample.pdf", Glide will load a file
with this name: "sample_thumbnail.jpg",
that contains first page of pdf file (preview)
(A single tap on one element will download the file
and launch an intent to display it)
*/
if (item.getType().equals("jpg") || item.getType().equals("jpeg") || item.getType().equals("png")) {
Glide.with(image.getContext()).load(item.getPath()).diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(image);
} else if (item.getType().equals("pdf")) {
Glide.with(image.getContext()).load(getName(item.getPath()) + "_thumbnail.jpg").diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(image);
} else {
throw new Exception("File type not supported");
}
Although maybe not everyone can have Imagick on the server, in my case this solution has worked wonderfully.
I've seen several questions on this but all of the solutions didn't work for me. For a client we have to develop an app that actually does nothing except of showing a WebView and a native DrawerLayout. However, we only have their mobile webpage (with a menu, etc.). So we have to hide some elements. It is very important that the existing stylesheets stay the same, just some other CSS rules are added.
What I've tried so far:
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (!mErrorOccured && !noConnectionAvailable) {
injectCSS();
}
mainActivity.hideLoadingScreen();
}
With this Injection:
// Inject CSS method: read style.css/readmode.css from assets folder
// Append stylesheet to document head
private void injectCSS() {
try {
Activity activity = (Activity) mContext;
SharedPreferences sharPref = activity.getSharedPreferences(Constants.PREFERENCE_NAME, Context.MODE_PRIVATE);
Boolean isReadMode = sharPref.getBoolean(Constants.READMODE_KEY, false);
InputStream inputStream;
if (isReadMode) {
inputStream = activity.getAssets().open("readmode.css");
} else {
inputStream = activity.getAssets().open("style.css");
}
byte[] buffer = new byte[inputStream.available()];
inputStream.read(buffer);
inputStream.close();
String encoded = Base64.encodeToString(buffer, Base64.NO_WRAP);
webView.loadUrl("javascript:(function() {" +
"var parent = document.getElementsByTagName('head').item(0);" +
"var style = document.createElement('style');" +
"style.type = 'text/css';" +
// Tell the browser to BASE64-decode the string into your script !!!
"style.innerHTML = window.atob('" + encoded + "');" +
"parent.appendChild(style);" +
"Android.injectCSS('Works!');})()");
} catch (Exception e) {
e.printStackTrace();
}
}
I've also tried to add a JavaScript Interface that uses the Android.injectCSS('Works!'); of the JavaScript above combined with:
#JavascriptInterface
public void injectCSS(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_LONG).show();
mMainActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
mMainFragment.unhideWebView();
}
});
}
And:
public void unhideWebView() {
webView.setVisibility(View.VISIBLE);
}
However, there is always a delay before the elements of the web page are hidden. I've also tried to use Jsoup. First this threw an NetworkOnMainThreadException. After I've tried to use it with an AsyncTask, it was not possible to change the WebView on onPostExecute() because this handling must be on the main thread. Even using a runOnUiThrad() did not help calling loadData() on the WebView with the new loaded data.
Is there any way to inject CSS/JS before the WebView shows up?
I'm using Dropbox Android SDK for getting photos and show it into a GridView.
Currently, I'm using Picasso for adapter with another images resources like GPhotos, Facebook... too.
How can I get downloadable link from Dropbox's file? Or How to use Picasso load Dropbox's image?
This is the way I get the Dropbox image:
List<Entry> listEntry = mApi.search("/photos", ".jpg", 0, false);
int i = 0;
for (Entry entry : listEntry) {
if (this.isRemoving()) { // Check if fragment is being removed
return;
}
if (!entry.isDir) { // Check if this entry is dir or file
i++;
Log.e(TAG, entry + " --- " + entry.fileName() + " --- "
+ entry.parentPath() + " --- " + entry.modified);
final DropboxLink shareLink = mApi.share(entry.parentPath()
+ entry.fileName());
url = shareLink.url;
Log.v(TAG, "shareLink: " + shareLink.url);
// The file name I get is ImageName.JPG and path is /Photos
// With share() the url I get looks like https://db.tt/xxxxxxxx
}
}
I can't offer insight about using Picasso, but if you can accept the file data directly, using getFile or getThumbnail instead of share would be better.
If you do need a URL that Picasso can directly download from, you should use the media method instead.
Thanks #Greg for suggestion, i have a little notice for using Picasso with Dropbox SDK.
When use media i have to use option TRUE in SSL for success loading image from Picasso, if FALSE Picasso cannot load links.
There is a sample from dropbox about this, try to check this link dropbox sample
there is 2 files that you need to see PicassoClient.java And FileThumbnailRequestHandler.java
here's the code :
PicassoClient Class :
public class PicassoClient {
private static Picasso sPicasso;
public static void init(Context context, DbxClientV2 dbxClient) {
// Configure picasso to know about special thumbnail requests
sPicasso = new Picasso.Builder(context)
.downloader(new OkHttpDownloader(context))
.addRequestHandler(new FileThumbnailRequestHandler(dbxClient))
.build();
}
public static Picasso getPicasso() {
return sPicasso;
}
}
FileThumbnailRequestHandler class :
public class FileThumbnailRequestHandler extends RequestHandler {
private static final String SCHEME = "dropbox";
private static final String HOST = "dropbox";
private final DbxClientV2 mDbxClient;
public FileThumbnailRequestHandler(DbxClientV2 dbxClient) {
mDbxClient = dbxClient;
}
/**
* Builds a {#link Uri} for a Dropbox file thumbnail suitable for handling by this handler
*/
public static Uri buildPicassoUri(FileMetadata file) {
return new Uri.Builder()
.scheme(SCHEME)
.authority(HOST)
.path(file.getPathLower()).build();
}
#Override
public boolean canHandleRequest(Request data) {
return SCHEME.equals(data.uri.getScheme()) && HOST.equals(data.uri.getHost());
}
#Override
public Result load(Request request, int networkPolicy) throws IOException {
try {
DbxDownloader<FileMetadata> downloader =
mDbxClient.files().getThumbnailBuilder(request.uri.getPath())
.withFormat(ThumbnailFormat.JPEG)
.withSize(ThumbnailSize.W1024H768)
.start();
return new Result(downloader.getInputStream(), Picasso.LoadedFrom.NETWORK);
} catch (DbxException e) {
throw new IOException(e);
}
}
}
You just need to import those 2 classes that i mentioned above then get the sPicasso object then you're ready to use it . :)
This question was asked here: Universal-Image-Loader: wrong Bitmaps are attached to ImageView
I am using the the latest, 1.9.3. I implemented the solution by having this in my application class:
#Override
public void onCreate() {
super.onCreate();
DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder().resetViewBeforeLoading(true).build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
.defaultDisplayImageOptions(defaultOptions)
.build();
ImageLoader.getInstance().init(config);
}
Per Android-Universal-Image-Loader doesn't keep loaded images on scroll in gridview, I load the image like so in my adapter:
ImageAware imageAware = new ImageViewAware(viewHolder.profileIV, false);
ImageLoader.getInstance().displayImage(imgUrl, imageAware);
It still doesn't work; I'm wondering if it's because I have to make a call to get the image url since the api that supplied the model data did not include an image url.
So in my adapter's getView(), before I use imageloader with the image url, I do another asynch call to get the image url, like so:
APIclient.getImageJson(getContext(), googleUrl, new JsonHttpResponseHandler() {
#Override
public void onSuccess(JSONObject imgJson) {
try {
JSONObject responseDataValue = imgJson.getJSONObject("responseData");
JSONArray resultsValue = responseDataValue.getJSONArray("results");
JSONObject result = resultsValue.getJSONObject(0);
String imgUrl = result.getString("url");
ImageAware imageAware = new ImageViewAware(viewHolder.profileIV, false);
ImageLoader.getInstance().displayImage(imgUrl, imageAware);
//ImageLoader.getInstance().displayImage(imgUrl, viewHolder.profileIV);
}
catch(Exception e) {
e.printStackTrace();
}
}
google url here looks like: https://ajax.googleapis.com/ajax/services/search/images?rsz=1&start=1&v=1.0&q=%22barack%20obama%22
each row would have a different url since the names are different. I don't know if the problem is still a listview recycer problem that wasn't fixed in the universal image loader library or if the culprit lies in the additional network call. How can I make the thumbnails consistent with the data next to it?
I think it's because of async call APIclient.getImageJson(...) in getView(). It's unknown time when onSuccess() callback is fired so you can call ImageLoader.getInstance().displayImage(...) for already recycled ImageView.
I can propose you join these 2 async operations (get JSON by APIclient, load image by ImageLoader) following way. Implement own ImageDoanloader which will process google JSON URLs (https://ajax.googleapis.com/ajax/services/search/images?rsz=1&start=1&v=1.0&q=%22barack%20obama%22), extract image URL and load image.
Let's introduce our own URI scheme - "json". So we know that incoming URIs like "json://..." are correspond to JSON link.
Prepare own downloader:
public class JsonImageDownloader extends BaseImageDownloader {
public static final String SCHEME_JSON = "json";
public static final String SCHEME_JSON_PREFIX = SCHEME_JSON + "://";
public JsonImageDownloader(Context context) {
super(context);
}
public JsonImageDownloader(Context context, int connectTimeout, int readTimeout) {
super(context, connectTimeout, readTimeout);
}
#Override
public InputStream getStream(String uri, Object extra) throws IOException {
if (uri.startsWith(SCHEME_JSON_PREFIX)) {
String jsonUri = uri.substring(SCHEME_JSON_PREFIX.length());
JSONObject imgJson = APIclient.getImageJson(context, jsonUri); // sync JSON loading
try {
JSONObject responseDataValue = imgJson.getJSONObject("responseData");
JSONArray resultsValue = responseDataValue.getJSONArray("results");
JSONObject result = resultsValue.getJSONObject(0);
String imgUrl = result.getString("url");
return super.getStream(imgUrl, extra);
} catch (JSONException e) {
throw new RuntimeException(e);
}
} else {
return super.getStream(uri, extra);
}
}
}
Set it into configuration
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
.imageDownloader(new JsonImageDownloader(context))
....
.build();
Use ImageLoader in getView(...) without using APIclient:
ImageAware imageAware = new ImageViewAware(viewHolder.profileIV, false);
ImageLoader.getInstance().displayImage("json://" + googleUrl, imageAware);
It seems that I am unable to set arbitrary query parameters to a #Get declaration
My endpoint looks like
http://api.lmiforall.org.uk/api/v1/ashe/estimateHours?soc=2349&coarse=true
There are a non trivial amount of parameters to this query, is there a declaration I can use to indicate this to the #Rest interface?
I tried declaring it as this, but it complains about fields being unused.
#Get("estimateHours")
ASHEFilterInfo GetEstimateHours( int soc, boolean coarse, String filters, String breakdown);
java: #org.androidannotations.annotations.rest.Get annotated method has only url variables in the method parameters
Look at AA cookbook.
Try this (not tested):
#Rest(rootUrl = "http://api.lmiforall.org.uk/api/v1/ashe")
public interface MyService {
#Get("/estimateHours?soc={soc}&coarse={coarse}&breakdown={breakdonw}&filters={filters}")
ASHEFilterInfo GetEstimateHoursFiltered( int soc, boolean coarse, String filters, String breakdown);
#Get("/estimateHours?soc={soc}&coarse={coarse}&breakdown={breakdonw}")
ASHEFilterInfo GetEstimateHours( int soc, boolean coarse, String breakdown);
}
When I needed to create #Get request with many dynamic parameteres, and some of them could be duplicated, I had resolved that problem so:
#Rest(rootUrl = "http://example.com:9080/",
converters = { GsonHttpMessageConverter.class },
interceptors = { ApiInterceptor.class })
public interface ExampleApi {
#Get("content/home/product-type/list?{filters}&domain={domain}") //filters is String like "param1=value1¶m1=value2¶m3=value3"
ProductTypeListResponse getProductTypeList(int domain, String filters);
}
public class ApiInterceptor implements ClientHttpRequestInterceptor {
private static final String TAG = ApiInterceptor.class.getSimpleName();
#Override
public ClientHttpResponse intercept(final HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
final QueryMultiParamsHttpRequest modifiedRequest = new QueryMultiParamsHttpRequest(request);
return execution.execute(modifiedRequest, body);
}
}
public class QueryMultiParamsHttpRequest implements HttpRequest {
private static final String TAG = QueryParametersBuilder.class.getSimpleName();
private HttpRequest httpRequest;
public QueryMultiParamsHttpRequest(final HttpRequest httpRequest) {
this.httpRequest = httpRequest;
}
#Override
public HttpMethod getMethod() {
return httpRequest.getMethod();
}
#Override
public URI getURI() {
final URI originalURI = httpRequest.getURI();
final String query = originalURI.getQuery() != null ? originalURI.getQuery().replace("%3D", "=").replace("%26", "&") : null;
URI newURI = null;
try {
newURI = new URI(originalURI.getScheme(), originalURI.getUserInfo(), originalURI.getHost(), originalURI.getPort(), originalURI.getPath(),
query, originalURI.getFragment());
} catch (URISyntaxException e) {
Log.e(TAG, "Error while creating URI of QueryMultiParamsHttpRequest", e);
}
return newURI;
}
#Override
public HttpHeaders getHeaders() {
return httpRequest.getHeaders();
}
}
So, I created a wrapper for HttpRequest, that can decode symbols "=" and "&". And this wrapper replaces original HttpRequest in ApiInterceptor. This is a little hacky solution, but it works.
I ran into this same issue and came up with a another solution that while far from ideal, works. The particular problem I was trying to solve was handling "HATEOAS" links.
What I ended up doing was creating a separate class called HATEOASClient to contain endpoint methods that would not escape the HATEOAS links passed in as params. To do that I basically just looked at an auto generated endpoint method and coped/tweaked the body in my implementation.
These methods use the same RestTemplate instance AndroidAnnotations sets up so you still get access to all the general setup you do on the RestTemplate.
For example:
public ResponseEntity<Foo> postFoo(Foo foo) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set(RestHeader.AUTH_TOKEN_HEADER, getClient().getHeader(RestHeader.AUTH_TOKEN_HEADER));
httpHeaders.set(RestHeader.ACCEPT_LANGUAGE_HEADER, getClient().getHeader(RestHeader.ACCEPT_LANGUAGE_HEADER));
httpHeaders.setAuthorization(authentication);
HttpEntity<Foo> requestEntity = new HttpEntity<>(null, httpHeaders);
HashMap<String, Object> urlVariables = new HashMap<>();
urlVariables.put("link", foo.getLinks().getFooCreate().getHref());
URI expanded = new UriTemplate(getClient().getRootUrl().
concat(API_VERSION + "{link}")).expand(urlVariables);
final String url;
try {
url = URLDecoder.decode(expanded.toString(), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return getClient().getRestTemplate().
exchange(url, HttpMethod.POST, requestEntity, Foo.class, urlVariables);
}
If all parameters is required you can use #Path annotation.
#Rest(rootUrl = "http://api.lmiforall.org.uk/api/v1/ashe")
public interface MyService {
#Get("/estimateHours?soc={soc}&coarse={coarse}&breakdown={breakdown}&filters={filters}")
ASHEFilterInfo GetEstimateHours(#Path int soc, #Path boolean coarse, #Path String breakdown, #Path String filters);
}
If one of the parameters is optional, there isn't yet a solution that can you can easily pass parameters using Android Annotations. But anybody can contribute to better Android Annotations.
if you define the params for each method then you need to provide them in each request. I thought this was sort of over kill too so what I did was just make a generic get/post request in my api client then just manually enter the values, if you don't define the root url I suppose you could use the QueryStringBuilder class and build the uri that way.
#Rest(rootUrl = "https://path/to/api/", converters = { FormHttpMessageConverter.class,
GsonHttpMessageConverter.class, ByteArrayHttpMessageConverter.class })
public interface ApiClient {
#Get("{uri}")
JsonElement apiGet(String uri);
#Post("{uri}")
JsonObject apiPost(String uri,MultiValueMap data);
RestTemplate getRestTemplate();
void setRootUrl(String rootUrl);
void setRestTemplate(RestTemplate restTemplate);
}
Example usage
JsonElement resp = apiClient.apiGet("method/?random_param=1&another_param=test);
It's not as clean but can be dynamic