Newer
Older
Telegram / TMessagesProj / src / main / java / org / telegram / ui / Components / WebPlayerView.java
ubt on 31 Oct 2017 85 KB init
/*
 * This is the source code of Telegram for Android v. 3.x.x.
 * It is licensed under GNU GPL v. 2 or later.
 * You should have received a copy of the license in this archive (see LICENSE).
 *
 * Copyright Nikolai Kudashov, 2013-2017.
 */

package org.telegram.ui.Components;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Base64;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.FrameLayout;
import android.widget.ImageView;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.Bitmaps;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.R;
import org.telegram.messenger.Utilities;
import org.telegram.messenger.exoplayer2.C;
import org.telegram.messenger.exoplayer2.ExoPlayer;
import org.telegram.messenger.exoplayer2.ui.AspectRatioFrameLayout;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLRPC;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.Semaphore;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;

@TargetApi(16)
public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerDelegate, AudioManager.OnAudioFocusChangeListener {

    public interface WebPlayerViewDelegate {
        void onInitFailed();
        TextureView onSwitchToFullscreen(View controlsView, boolean fullscreen, float aspectRatio, int rotation, boolean byButton);
        TextureView onSwitchInlineMode(View controlsView, boolean inline, float aspectRatio, int rotation, boolean animated);
        void onInlineSurfaceTextureReady();
        void prepareToSwitchInlineMode(boolean inline, Runnable switchInlineModeRunnable, float aspectRatio, boolean animated);
        void onSharePressed();
        void onPlayStateChanged(WebPlayerView playerView, boolean playing);
        void onVideoSizeChanged(float aspectRatio, int rotation);
        ViewGroup getTextureViewContainer();
        boolean checkInlinePermissons();
    }

    private VideoPlayer videoPlayer;
    private WebView webView;
    private String interfaceName;
    private AspectRatioFrameLayout aspectRatioFrameLayout;
    private TextureView textureView;
    private ImageView textureImageView;
    private ViewGroup textureViewContainer;
    private Bitmap currentBitmap;
    private TextureView changedTextureView;
    private int waitingForFirstTextureUpload;
    private boolean isAutoplay;
    private WebPlayerViewDelegate delegate;
    private boolean initFailed;
    private boolean initied;
    private String playVideoUrl;
    private String playVideoType;
    private String playAudioUrl;
    private String playAudioType;

    private boolean allowInlineAnimation = Build.VERSION.SDK_INT >= 21;

    private static final int AUDIO_NO_FOCUS_NO_DUCK = 0;
    private static final int AUDIO_NO_FOCUS_CAN_DUCK = 1;
    private static final int AUDIO_FOCUSED  = 2;
    private boolean hasAudioFocus;
    private int audioFocus;
    private boolean resumeAudioOnFocusGain;

    private long lastUpdateTime;
    private boolean firstFrameRendered;
    private float currentAlpha;

    private int seekToTime;

    private boolean drawImage;

    private Paint backgroundPaint = new Paint();

    private AsyncTask currentTask;

    private boolean changingTextureView;
    private boolean inFullscreen;
    private boolean isInline;
    private boolean isCompleted;
    private boolean isLoading;
    private boolean switchingInlineMode;

    private RadialProgressView progressView;
    private ImageView fullscreenButton;
    private ImageView playButton;
    private ImageView inlineButton;
    private ImageView shareButton;
    private AnimatorSet progressAnimation;

    private ControlsView controlsView;

    private Runnable progressRunnable = new Runnable() {
        @Override
        public void run() {
            if (videoPlayer == null || !videoPlayer.isPlaying()) {
                return;
            }
            controlsView.setProgress((int) (videoPlayer.getCurrentPosition() / 1000));
            controlsView.setBufferedProgress((int) (videoPlayer.getBufferedPosition() / 1000), videoPlayer.getBufferedPercentage());

            AndroidUtilities.runOnUIThread(progressRunnable, 1000);
        }
    };

    private static final Pattern youtubeIdRegex = Pattern.compile("(?:youtube(?:-nocookie)?\\.com/(?:[^/\\n\\s]+/\\S+/|(?:v|e(?:mbed)?)/|\\S*?[?&]v=)|youtu\\.be/)([a-zA-Z0-9_-]{11})");
    private static final Pattern vimeoIdRegex = Pattern.compile("https?://(?:(?:www|(player))\\.)?vimeo(pro)?\\.com/(?!(?:channels|album)/[^/?#]+/?(?:$|[?#])|[^/]+/review/|ondemand/)(?:.*?/)?(?:(?:play_redirect_hls|moogaloop\\.swf)\\?clip_id=)?(?:videos?/)?([0-9]+)(?:/[\\da-f]+)?/?(?:[?&].*)?(?:[#].*)?$");
    private static final Pattern coubIdRegex = Pattern.compile("(?:coub:|https?://(?:coub\\.com/(?:view|embed|coubs)/|c-cdn\\.coub\\.com/fb-player\\.swf\\?.*\\bcoub(?:ID|id)=))([\\da-z]+)");
    private static final Pattern aparatIdRegex = Pattern.compile("^https?://(?:www\\.)?aparat\\.com/(?:v/|video/video/embed/videohash/)([a-zA-Z0-9]+)");

    private static final Pattern aparatFileListPattern = Pattern.compile("fileList\\s*=\\s*JSON\\.parse\\('([^']+)'\\)");

    private static final Pattern stsPattern = Pattern.compile("\"sts\"\\s*:\\s*(\\d+)");
    private static final Pattern jsPattern = Pattern.compile("\"assets\":.+?\"js\":\\s*(\"[^\"]+\")");
    private static final Pattern sigPattern = Pattern.compile("\\.sig\\|\\|([a-zA-Z0-9$]+)\\(");
    private static final Pattern sigPattern2 = Pattern.compile("[\"']signature[\"']\\s*,\\s*([a-zA-Z0-9$]+)\\(");
    private static final Pattern stmtVarPattern = Pattern.compile("var\\s");
    private static final Pattern stmtReturnPattern = Pattern.compile("return(?:\\s+|$)");
    private static final Pattern exprParensPattern = Pattern.compile("[()]");
    private static final Pattern playerIdPattern = Pattern.compile(".*?-([a-zA-Z0-9_-]+)(?:/watch_as3|/html5player(?:-new)?|(?:/[a-z]{2}_[A-Z]{2})?/base)?\\.([a-z]+)$");
    private static final String exprName = "[a-zA-Z_$][a-zA-Z_$0-9]*";

    private abstract class function {
        public abstract Object run(Object[] args);
    }

    private class JSExtractor {

        ArrayList<String> codeLines = new ArrayList<>();

        private String jsCode;
        private String[] operators = {"|", "^", "&", ">>", "<<", "-", "+", "%", "/", "*"};
        private String[] assign_operators = {"|=", "^=", "&=", ">>=", "<<=", "-=", "+=", "%=", "/=", "*=", "="};

        public JSExtractor(String js) {
            jsCode = js;
        }

        private void interpretExpression(String expr, HashMap<String, String> localVars, int allowRecursion) throws Exception {
            expr = expr.trim();
            if (TextUtils.isEmpty(expr)) {
                return;
            }
            if (expr.charAt(0) == '(') {
                int parens_count = 0;
                Matcher matcher = exprParensPattern.matcher(expr);
                while (matcher.find()) {
                    String group = matcher.group(0);
                    if (group.indexOf('0') == '(') {
                        parens_count++;
                    } else {
                        parens_count--;
                        if (parens_count == 0) {
                            String sub_expr = expr.substring(1, matcher.start());
                            interpretExpression(sub_expr, localVars, allowRecursion);
                            String remaining_expr = expr.substring(matcher.end()).trim();
                            if (TextUtils.isEmpty(remaining_expr)) {
                                return;
                            } else {
                                expr = remaining_expr;
                            }
                            break;
                        }
                    }
                }
                if (parens_count != 0) {
                    throw new Exception(String.format("Premature end of parens in %s", expr));
                }
            }
            for (int a = 0; a < assign_operators.length; a++) {
                String func = assign_operators[a];
                Matcher matcher = Pattern.compile(String.format(Locale.US, "(?x)(%s)(?:\\[([^\\]]+?)\\])?\\s*%s(.*)$", exprName, Pattern.quote(func))).matcher(expr);
                if (!matcher.find()) {
                    continue;
                }
                interpretExpression(matcher.group(3), localVars, allowRecursion - 1);
                String index = matcher.group(2);
                if (!TextUtils.isEmpty(index)) {
                    interpretExpression(index, localVars, allowRecursion);
                } else {
                    localVars.put(matcher.group(1), "");
                }
                return;
            }

            try {
                Integer.parseInt(expr);
                return;
            } catch (Exception e) {
                //ignore
            }

            Matcher matcher = Pattern.compile(String.format(Locale.US, "(?!if|return|true|false)(%s)$", exprName)).matcher(expr);
            if (matcher.find()) {
                return;
            }

            if (expr.charAt(0) == '"' && expr.charAt(expr.length() - 1) == '"') {
                return;
            }
            try {
                new JSONObject(expr).toString();
                return;
            } catch (Exception e) {
                //ignore
            }

            matcher = Pattern.compile(String.format(Locale.US, "(%s)\\[(.+)\\]$", exprName)).matcher(expr);
            if (matcher.find()) {
                String val = matcher.group(1);
                interpretExpression(matcher.group(2), localVars, allowRecursion - 1);
                return;
            }

            matcher = Pattern.compile(String.format(Locale.US, "(%s)(?:\\.([^(]+)|\\[([^]]+)\\])\\s*(?:\\(+([^()]*)\\))?$", exprName)).matcher(expr);
            if (matcher.find()) {
                String variable = matcher.group(1);
                String m1 = matcher.group(2);
                String m2 = matcher.group(3);
                String member = (TextUtils.isEmpty(m1) ? m2 : m1).replace("\"", "");
                String arg_str = matcher.group(4);
                if (localVars.get(variable) == null) {
                    extractObject(variable);
                }
                if (arg_str == null) {
                    return;
                }
                if (expr.charAt(expr.length() - 1) != ')') {
                    throw new Exception("last char not ')'");
                }
                String argvals[];
                if (arg_str.length() != 0) {
                    String[] args = arg_str.split(",");
                    for (int a = 0; a < args.length; a++) {
                        interpretExpression(args[a], localVars, allowRecursion);
                    }
                }
                return;
            }

            matcher = Pattern.compile(String.format(Locale.US, "(%s)\\[(.+)\\]$", exprName)).matcher(expr);
            if (matcher.find()) {
                Object val = localVars.get(matcher.group(1));
                interpretExpression(matcher.group(2), localVars, allowRecursion - 1);
                return;
            }

            for (int a = 0; a < operators.length; a++) {
                String func = operators[a];
                matcher = Pattern.compile(String.format(Locale.US, "(.+?)%s(.+)", Pattern.quote(func))).matcher(expr);
                if (!matcher.find()) {
                    continue;
                }
                boolean[] abort = new boolean[1];
                interpretStatement(matcher.group(1), localVars, abort, allowRecursion - 1);
                if (abort[0]) {
                    throw new Exception(String.format("Premature left-side return of %s in %s", func, expr));
                }
                interpretStatement(matcher.group(2), localVars, abort, allowRecursion - 1);
                if (abort[0]) {
                    throw new Exception(String.format("Premature right-side return of %s in %s", func, expr));
                }
            }

            matcher = Pattern.compile(String.format(Locale.US, "^(%s)\\(([a-zA-Z0-9_$,]*)\\)$", exprName)).matcher(expr);
            if (matcher.find()) {
                String fname = matcher.group(1);
                extractFunction(fname);
            }
            throw new Exception(String.format("Unsupported JS expression %s", expr));
        }

        private void interpretStatement(String stmt, HashMap<String, String> localVars, boolean[] abort, int allowRecursion) throws Exception {
            if (allowRecursion < 0) {
                throw new Exception("recursion limit reached");
            }
            abort[0] = false;
            stmt = stmt.trim();
            Matcher matcher = stmtVarPattern.matcher(stmt);
            String expr;
            if (matcher.find()) {
                expr = stmt.substring(matcher.group(0).length());
            } else {
                matcher = stmtReturnPattern.matcher(stmt);
                if (matcher.find()) {
                    expr = stmt.substring(matcher.group(0).length());
                    abort[0] = true;
                } else {
                    expr = stmt;
                }
            }
            interpretExpression(expr, localVars, allowRecursion);
        }

        private HashMap<String, Object> extractObject(String objname) throws Exception {
            String funcName =  "(?:[a-zA-Z$0-9]+|\"[a-zA-Z$0-9]+\"|'[a-zA-Z$0-9]+')";
            HashMap<String, Object> obj = new HashMap<>();
            //                                                                                         ?P<fields>
            Matcher matcher = Pattern.compile(String.format(Locale.US, "(?:var\\s+)?%s\\s*=\\s*\\{\\s*((%s\\s*:\\s*function\\(.*?\\)\\s*\\{.*?\\}(?:,\\s*)?)*)\\}\\s*;", Pattern.quote(objname), funcName)).matcher(jsCode);
            String fields = null;
            while (matcher.find()) {
                String code = matcher.group();
                fields = matcher.group(2);
                if (TextUtils.isEmpty(fields)) {
                    continue;
                }
                if (!codeLines.contains(code)) {
                    codeLines.add(matcher.group());
                }
                break;
            }
            //                          ?P<key>                            ?P<args>     ?P<code>
            matcher = Pattern.compile(String.format("(%s)\\s*:\\s*function\\(([a-z,]+)\\)\\{([^}]+)\\}", funcName)).matcher(fields);
            while (matcher.find()) {
                String[] argnames = matcher.group(2).split(",");
                buildFunction(argnames, matcher.group(3));
            }
            return obj;
        }

        private void buildFunction(String[] argNames, String funcCode) throws Exception {
            HashMap<String, String> localVars = new HashMap<>();
            for (int a = 0; a < argNames.length; a++) {
                localVars.put(argNames[a], "");
            }
            String stmts[] = funcCode.split(";");
            boolean abort[] = new boolean[1];
            for (int a = 0; a < stmts.length; a++) {
                interpretStatement(stmts[a], localVars, abort, 100);
                if (abort[0]) {
                    return;
                }
            }
        }

        private String extractFunction(String funcName) throws Exception {
            try {
                String quote = Pattern.quote(funcName);
                Pattern funcPattern = Pattern.compile(String.format(Locale.US, "(?x)(?:function\\s+%s|[{;,]\\s*%s\\s*=\\s*function|var\\s+%s\\s*=\\s*function)\\s*\\(([^)]*)\\)\\s*\\{([^}]+)\\}", quote, quote, quote));
                Matcher matcher = funcPattern.matcher(jsCode);
                if (matcher.find()) {
                    String group = matcher.group();
                    if (!codeLines.contains(group)) {
                        codeLines.add(group + ";");
                    }
                    buildFunction(matcher.group(1).split(","), matcher.group(2));
                }
            } catch (Exception e) {
                codeLines.clear();
                FileLog.e(e);
            }
            return TextUtils.join("", codeLines);
        }
    }

    public interface CallJavaResultInterface {
        void jsCallFinished(String value);
    }

    public class JavaScriptInterface {
        private final CallJavaResultInterface callJavaResultInterface;

        public JavaScriptInterface(CallJavaResultInterface callJavaResult) {
            callJavaResultInterface = callJavaResult;
        }

        @JavascriptInterface
        public void returnResultToJava(String value) {
            callJavaResultInterface.jsCallFinished(value);
        }
    }

    protected String downloadUrlContent(AsyncTask parentTask, String url) {
        boolean canRetry = true;
        InputStream httpConnectionStream = null;
        boolean done = false;
        StringBuilder result = null;
        URLConnection httpConnection = null;
        try {
            URL downloadUrl = new URL(url);
            httpConnection = downloadUrl.openConnection();
            httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)");
            httpConnection.addRequestProperty("Accept-Encoding", "gzip, deflate");
            httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5");
            httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
            httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
            httpConnection.setConnectTimeout(5000);
            httpConnection.setReadTimeout(5000);
            if (httpConnection instanceof HttpURLConnection) {
                HttpURLConnection httpURLConnection = (HttpURLConnection) httpConnection;
                httpURLConnection.setInstanceFollowRedirects(true);
                int status = httpURLConnection.getResponseCode();
                if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_SEE_OTHER) {
                    String newUrl = httpURLConnection.getHeaderField("Location");
                    String cookies = httpURLConnection.getHeaderField("Set-Cookie");
                    downloadUrl = new URL(newUrl);
                    httpConnection = downloadUrl.openConnection();
                    httpConnection.setRequestProperty("Cookie", cookies);
                    httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)");
                    httpConnection.addRequestProperty("Accept-Encoding", "gzip, deflate");
                    httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5");
                    httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
                    httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
                }
            }
            httpConnection.connect();
            httpConnectionStream = new GZIPInputStream(httpConnection.getInputStream());
        } catch (Throwable e) {
            if (e instanceof SocketTimeoutException) {
                if (ConnectionsManager.isNetworkOnline()) {
                    canRetry = false;
                }
            } else if (e instanceof UnknownHostException) {
                canRetry = false;
            } else if (e instanceof SocketException) {
                if (e.getMessage() != null && e.getMessage().contains("ECONNRESET")) {
                    canRetry = false;
                }
            } else if (e instanceof FileNotFoundException) {
                canRetry = false;
            }
            FileLog.e(e);
        }

        if (canRetry) {
            try {
                if (httpConnection != null && httpConnection instanceof HttpURLConnection) {
                    int code = ((HttpURLConnection) httpConnection).getResponseCode();
                    if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) {
                        //canRetry = false;
                    }
                }
            } catch (Exception e) {
                FileLog.e(e);
            }

            if (httpConnectionStream != null) {
                try {
                    byte[] data = new byte[1024 * 32];
                    while (true) {
                        if (parentTask.isCancelled()) {
                            break;
                        }
                        try {
                            int read = httpConnectionStream.read(data);
                            if (read > 0) {
                                if (result == null) {
                                    result = new StringBuilder();
                                }
                                result.append(new String(data, 0, read, "UTF-8"));
                            } else if (read == -1) {
                                done = true;
                                break;
                            } else {
                                break;
                            }
                        } catch (Exception e) {
                            FileLog.e(e);
                            break;
                        }
                    }
                } catch (Throwable e) {
                    FileLog.e(e);
                }
            }

            try {
                if (httpConnectionStream != null) {
                    httpConnectionStream.close();
                }
            } catch (Throwable e) {
                FileLog.e(e);
            }
        }
        return done ? result.toString() : null;
    }

    private class YoutubeVideoTask extends AsyncTask<Void, Void, String> {

        private String videoId;
        private boolean canRetry = true;
        private Semaphore semaphore = new Semaphore(0);
        private String[] result = new String[1];
        private String sig;

        public YoutubeVideoTask(String vid) {
            videoId = vid;
        }

        @Override
        protected String doInBackground(Void... voids) {
            Matcher matcher;
            String embedCode = downloadUrlContent(this, "https://www.youtube.com/embed/" + videoId);
            if (isCancelled()) {
                return null;
            }
            String params = "video_id=" + videoId + "&ps=default&gl=US&hl=en";
            try {
                params += "&eurl=" + URLEncoder.encode("https://youtube.googleapis.com/v/" + videoId, "UTF-8");
            } catch (Exception e) {
                FileLog.e(e);
            }
            if (embedCode != null) {
                matcher = stsPattern.matcher(embedCode);
                if (matcher.find()) {
                    params += "&sts=" + embedCode.substring(matcher.start() + 6, matcher.end());
                } else {
                    params += "&sts=";
                }
            }

            boolean encrypted = false;
            String extra[] = new String[] {"", "&el=info", "&el=embedded", "&el=detailpage", "&el=vevo"};
            for (int i = 0; i < extra.length; i++) {
                String videoInfo = downloadUrlContent(this, "https://www.youtube.com/get_video_info?" + params + extra[i]);
                if (isCancelled()) {
                    return null;
                }
                boolean exists = false;
                if (videoInfo != null) {
                    String args[] = videoInfo.split("&");
                    for (int a = 0; a < args.length; a++) {
                        if (args[a].startsWith("dashmpd")) {
                            exists = true;
                            String args2[] = args[a].split("=");
                            if (args2.length == 2) {
                                try {
                                    result[0] = URLDecoder.decode(args2[1], "UTF-8");
                                } catch (Exception e) {
                                    FileLog.e(e);
                                }
                            }
                        } else if (args[a].startsWith("use_cipher_signature")) {
                            String args2[] = args[a].split("=");
                            if (args2.length == 2) {
                                if (args2[1].toLowerCase().equals("true")) {
                                    encrypted = true;
                                }
                            }
                        }
                    }
                }
                if (exists) {
                    break;
                }
            }

            if (result[0] != null && (encrypted || result[0].contains("/s/")) && embedCode != null) {
                encrypted = true;
                int index = result[0].indexOf("/s/");
                int index2 = result[0].indexOf('/', index + 10);
                if (index != -1) {
                    if (index2 == -1) {
                        index2 = result[0].length();
                    }
                    sig = result[0].substring(index, index2);
                    String jsUrl = null;
                    matcher = jsPattern.matcher(embedCode);
                    if (matcher.find()) {
                        try {
                            JSONTokener tokener = new JSONTokener(matcher.group(1));
                            Object value = tokener.nextValue();
                            if (value instanceof String) {
                                jsUrl = (String) value;
                            }
                        } catch (Exception e) {
                            FileLog.e(e);
                        }
                    }

                    if (jsUrl != null) {
                        matcher = playerIdPattern.matcher(jsUrl);
                        String playerId;
                        if (matcher.find()) {
                            playerId = matcher.group(1) + matcher.group(2);
                        } else {
                            playerId = null;
                        }
                        String functionCode = null;
                        String functionName = null;
                        SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("youtubecode", Activity.MODE_PRIVATE);
                        if (playerId != null) {
                            functionCode = preferences.getString(playerId, null);
                            functionName = preferences.getString(playerId + "n", null);
                        }
                        if (functionCode == null) {
                            if (jsUrl.startsWith("//")) {
                                jsUrl = "https:" + jsUrl;
                            } else if (jsUrl.startsWith("/")) {
                                jsUrl = "https://www.youtube.com" + jsUrl;
                            }
                            String jsCode = downloadUrlContent(this, jsUrl);
                            if (isCancelled()) {
                                return null;
                            }
                            if (jsCode != null) {
                                matcher = sigPattern.matcher(jsCode);
                                if (matcher.find()) {
                                    functionName = matcher.group(1);
                                } else {
                                    matcher = sigPattern2.matcher(jsCode);
                                    if (matcher.find()) {
                                        functionName = matcher.group(1);
                                    }
                                }
                                if (functionName != null) {
                                    try {
                                        JSExtractor extractor = new JSExtractor(jsCode);
                                        functionCode = extractor.extractFunction(functionName);
                                        if (!TextUtils.isEmpty(functionCode) && playerId != null) {
                                            preferences.edit().putString(playerId, functionCode).putString(playerId + "n", functionName).commit();
                                        }
                                    } catch (Exception e) {
                                        FileLog.e(e);
                                    }
                                }
                            }
                        }
                        if (!TextUtils.isEmpty(functionCode)) {
                            if (Build.VERSION.SDK_INT >= 21) {
                                functionCode += functionName + "('" + sig.substring(3) + "');";
                            } else {
                                functionCode += "window." + interfaceName + ".returnResultToJava(" + functionName + "('" + sig.substring(3) + "'));";
                            }
                            final String functionCodeFinal = functionCode;
                            try {
                                AndroidUtilities.runOnUIThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (Build.VERSION.SDK_INT >= 21) {
                                            webView.evaluateJavascript(functionCodeFinal, new ValueCallback<String>() {
                                                @Override
                                                public void onReceiveValue(String value) {
                                                    result[0] = result[0].replace(sig, "/signature/" + value.substring(1, value.length() - 1));
                                                    semaphore.release();
                                                }
                                            });
                                        } else {
                                            try {
                                                String javascript = "<script>" + functionCodeFinal + "</script>";
                                                byte[] data = javascript.getBytes("UTF-8");
                                                final String base64 = Base64.encodeToString(data, Base64.DEFAULT);
                                                webView.loadUrl("data:text/html;charset=utf-8;base64," + base64);
                                            } catch (Exception e) {
                                                FileLog.e(e);
                                            }
                                        }
                                    }
                                });
                                semaphore.acquire();
                                encrypted = false;
                            } catch (Exception e) {
                                FileLog.e(e);
                            }
                        }
                    }
                }
            }
            return isCancelled() || encrypted ? null : result[0];
        }

        private void onInterfaceResult(String value) {
            result[0] = result[0].replace(sig, "/signature/" + value);
            semaphore.release();
        }

        @Override
        protected void onPostExecute(String result) {
            if (result != null) {
                initied = true;
                playVideoType = "dash";
                playVideoUrl = result;
                if (isAutoplay) {
                    preparePlayer();
                }
                showProgress(false, true);
                controlsView.show(true, true);
            } else if (!isCancelled()) {
                onInitFailed();
            }
        }
    }

    private class VimeoVideoTask extends AsyncTask<Void, Void, String> {

        private String videoId;
        private boolean canRetry = true;
        private String[] results = new String[2];

        public VimeoVideoTask(String vid) {
            videoId = vid;
        }

        protected String doInBackground(Void... voids) {
            String playerCode = downloadUrlContent(this, String.format(Locale.US, "https://player.vimeo.com/video/%s/config", videoId));
            if (isCancelled()) {
                return null;
            }
            try {
                JSONObject json = new JSONObject(playerCode);
                JSONObject files = json.getJSONObject("request").getJSONObject("files");
                if (files.has("hls")) {
                    JSONObject hls = files.getJSONObject("hls");
                    try {
                        results[0] = hls.getString("url");
                    } catch (Exception e) {
                        String defaultCdn = hls.getString("default_cdn");
                        JSONObject cdns = hls.getJSONObject("cdns");
                        hls = cdns.getJSONObject(defaultCdn);
                        results[0] = hls.getString("url");
                    }
                    results[1] = "hls";
                } else if (files.has("progressive")) {
                    results[1] = "other";
                    JSONArray progressive = files.getJSONArray("progressive");
                    for (int i = 0; i < progressive.length(); i++) {
                        JSONObject format = progressive.getJSONObject(i);
                        results[0] = format.getString("url");
                        break;
                    }
                }
            } catch (Exception e) {
                FileLog.e(e);
            }
            return isCancelled() ? null : results[0];
        }

        @Override
        protected void onPostExecute(String result) {
            if (result != null) {
                initied = true;
                playVideoUrl = result;
                playVideoType = results[1];
                if (isAutoplay) {
                    preparePlayer();
                }
                showProgress(false, true);
                controlsView.show(true, true);
            } else if (!isCancelled()) {
                onInitFailed();
            }
        }
    }

    private class AparatVideoTask extends AsyncTask<Void, Void, String> {

        private String videoId;
        private boolean canRetry = true;
        private String[] results = new String[2];

        public AparatVideoTask(String vid) {
            videoId = vid;
        }

        protected String doInBackground(Void... voids) {
            String playerCode = downloadUrlContent(this, String.format(Locale.US, "http://www.aparat.com/video/video/embed/vt/frame/showvideo/yes/videohash/%s", videoId));
            if (isCancelled()) {
                return null;
            }
            try {
                Matcher filelist = aparatFileListPattern.matcher(playerCode);
                if (filelist.find()) {
                    String jsonCode = filelist.group(1);
                    JSONArray json = new JSONArray(jsonCode);
                    for (int a = 0; a < json.length(); a++) {
                        JSONArray array = json.getJSONArray(a);
                        if (array.length() == 0) {
                            continue;
                        }
                        JSONObject object = array.getJSONObject(0);
                        if (!object.has("file")) {
                            continue;
                        }
                        results[0] = object.getString("file");
                        results[1] = "other";
                    }
                }
            } catch (Exception e) {
                FileLog.e(e);
            }
            return isCancelled() ? null : results[0];
        }

        @Override
        protected void onPostExecute(String result) {
            if (result != null) {
                initied = true;
                playVideoUrl = result;
                playVideoType = results[1];
                if (isAutoplay) {
                    preparePlayer();
                }
                showProgress(false, true);
                controlsView.show(true, true);
            } else if (!isCancelled()) {
                onInitFailed();
            }
        }
    }

    private class CoubVideoTask extends AsyncTask<Void, Void, String> {

        private String videoId;
        private boolean canRetry = true;
        private String[] results = new String[4];

        public CoubVideoTask(String vid) {
            videoId = vid;
        }

        protected String doInBackground(Void... voids) {
            String playerCode = downloadUrlContent(this, String.format(Locale.US, "https://coub.com/api/v2/coubs/%s.json", videoId));
            if (isCancelled()) {
                return null;
            }
            try {
                JSONObject json = new JSONObject(playerCode);
                String video = json.getString("file");
                String audio = json.getString("audio_file_url");
                if (video != null && audio != null) {
                    results[0] = video;
                    results[1] = "other";
                    results[2] = audio;
                    results[3] = "other";
                }
            } catch (Exception e) {
                FileLog.e(e);
            }
            return isCancelled() ? null : results[0];
        }

        @Override
        protected void onPostExecute(String result) {
            if (result != null) {
                initied = true;
                playVideoUrl = result;
                playVideoType = results[1];
                playAudioUrl = results[2];
                playAudioType = results[3];
                if (isAutoplay) {
                    preparePlayer();
                }
                showProgress(false, true);
                controlsView.show(true, true);
            } else if (!isCancelled()) {
                onInitFailed();
            }
        }
    }

    private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {

        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            if (changingTextureView) {
                if (switchingInlineMode) {
                    waitingForFirstTextureUpload = 2;
                }
                textureView.setSurfaceTexture(surface);
                textureView.setVisibility(VISIBLE);
                changingTextureView = false;
                return false;
            }
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
            if (waitingForFirstTextureUpload == 1) {
                changedTextureView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                    @Override
                    public boolean onPreDraw() {
                        changedTextureView.getViewTreeObserver().removeOnPreDrawListener(this);
                        if (textureImageView != null) {
                            textureImageView.setVisibility(INVISIBLE);
                            textureImageView.setImageDrawable(null);
                            if (currentBitmap != null) {
                                currentBitmap.recycle();
                                currentBitmap = null;
                            }
                        }
                        AndroidUtilities.runOnUIThread(new Runnable() {
                            @Override
                            public void run() {
                                delegate.onInlineSurfaceTextureReady();
                            }
                        });
                        waitingForFirstTextureUpload = 0;
                        return true;
                    }
                });
                changedTextureView.invalidate();
            }
        }
    };

    private Runnable switchToInlineRunnable = new Runnable() {
        @Override
        public void run() {
            switchingInlineMode = false;
            if (currentBitmap != null) {
                currentBitmap.recycle();
                currentBitmap = null;
            }

            changingTextureView = true;
            if (textureImageView != null) {
                try {
                    currentBitmap = Bitmaps.createBitmap(textureView.getWidth(), textureView.getHeight(), Bitmap.Config.ARGB_8888);
                    textureView.getBitmap(currentBitmap);
                } catch (Throwable e) {
                    if (currentBitmap != null) {
                        currentBitmap.recycle();
                        currentBitmap = null;
                    }
                    FileLog.e(e);
                }

                if (currentBitmap != null) {
                    textureImageView.setVisibility(VISIBLE);
                    textureImageView.setImageBitmap(currentBitmap);
                } else {
                    textureImageView.setImageDrawable(null);
                }
            }

            isInline = true;
            updatePlayButton();
            updateShareButton();
            updateFullscreenButton();
            updateInlineButton();

            ViewGroup viewGroup = (ViewGroup) controlsView.getParent();
            if (viewGroup != null) {
                viewGroup.removeView(controlsView);
            }
            changedTextureView = delegate.onSwitchInlineMode(controlsView, isInline, aspectRatioFrameLayout.getAspectRatio(), aspectRatioFrameLayout.getVideoRotation(), allowInlineAnimation);
            changedTextureView.setVisibility(INVISIBLE);
            ViewGroup parent = (ViewGroup) textureView.getParent();
            if (parent != null) {
                parent.removeView(textureView);
            }
            controlsView.show(false, false);
        }
    };

    private class ControlsView extends FrameLayout {

        private ImageReceiver imageReceiver;
        private boolean progressPressed;
        private TextPaint textPaint;
        private StaticLayout durationLayout;
        private StaticLayout progressLayout;
        private Paint progressPaint;
        private Paint progressInnerPaint;
        private Paint progressBufferedPaint;
        private int durationWidth;
        private int duration;
        private int progress;
        private int bufferedPosition;
        private int bufferedPercentage;
        private boolean isVisible = true;
        private AnimatorSet currentAnimation;
        private int lastProgressX;
        private int currentProgressX;
        private Runnable hideRunnable = new Runnable() {
            @Override
            public void run() {
                show(false, true);
            }
        };

        public ControlsView(Context context) {
            super(context);
            setWillNotDraw(false);

            textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
            textPaint.setColor(0xffffffff);
            textPaint.setTextSize(AndroidUtilities.dp(12));

            progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            progressPaint.setColor(0xff19a7e8);

            progressInnerPaint = new Paint();
            progressInnerPaint.setColor(0xff959197);

            progressBufferedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            progressBufferedPaint.setColor(0xffffffff);

            imageReceiver = new ImageReceiver(this);
        }

        public void setDuration(int value) {
            if (duration == value || value < 0) {
                return;
            }
            duration = value;
            durationLayout = new StaticLayout(String.format(Locale.US, "%d:%02d", duration / 60, duration % 60), textPaint, AndroidUtilities.dp(1000), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
            if (durationLayout.getLineCount() > 0) {
                durationWidth = (int) Math.ceil(durationLayout.getLineWidth(0));
            }
            invalidate();
        }

        public void setBufferedProgress(int position, int percentage) {
            bufferedPosition = position;
            bufferedPercentage = percentage;
            invalidate();
        }

        public void setProgress(int value) {
            if (progressPressed || value < 0) {
                return;
            }
            progress = value;
            progressLayout = new StaticLayout(String.format(Locale.US, "%d:%02d", progress / 60, progress % 60), textPaint, AndroidUtilities.dp(1000), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
            invalidate();
        }

        public void show(boolean value, boolean animated) {
            if (isVisible == value) {
                return;
            }
            isVisible = value;
            if (currentAnimation != null) {
                currentAnimation.cancel();
            }
            if (isVisible) {
                if (animated) {
                    currentAnimation = new AnimatorSet();
                    currentAnimation.playTogether(ObjectAnimator.ofFloat(this, "alpha", 1.0f));
                    currentAnimation.setDuration(150);
                    currentAnimation.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animator) {
                            currentAnimation = null;
                        }
                    });
                    currentAnimation.start();
                } else {
                    setAlpha(1.0f);
                }
            } else {
                if (animated) {
                    currentAnimation = new AnimatorSet();
                    currentAnimation.playTogether(ObjectAnimator.ofFloat(this, "alpha", 0.0f));
                    currentAnimation.setDuration(150);
                    currentAnimation.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animator) {
                            currentAnimation = null;
                        }
                    });
                    currentAnimation.start();
                } else {
                    setAlpha(0.0f);
                }
            }
            checkNeedHide();
        }

        private void checkNeedHide() {
            AndroidUtilities.cancelRunOnUIThread(hideRunnable);
            if (isVisible && videoPlayer.isPlaying()) {
                AndroidUtilities.runOnUIThread(hideRunnable, 3000);
            }
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                if (!isVisible) {
                    show(true, true);
                    return true;
                }
                onTouchEvent(ev);
                return progressPressed;
            }
            return super.onInterceptTouchEvent(ev);
        }

        @Override
        public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
            super.requestDisallowInterceptTouchEvent(disallowIntercept);
            checkNeedHide();
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            int progressLineX;
            int progressLineEndX;
            int progressY;
            if (inFullscreen) {
                progressLineX = AndroidUtilities.dp(18 + 18) + durationWidth;
                progressLineEndX = getMeasuredWidth() - AndroidUtilities.dp(58 + 18) - durationWidth;
                progressY = getMeasuredHeight() - AndroidUtilities.dp(7 + 21);
            } else {
                progressLineX = 0;
                progressLineEndX = getMeasuredWidth();
                progressY = getMeasuredHeight() - AndroidUtilities.dp(2 + 10);
            }

            int progressX = progressLineX + (duration != 0 ? (int) ((progressLineEndX - progressLineX) * (progress / (float) duration)) : 0);

            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                if (isVisible && !isInline) {
                    if (duration != 0) {
                        int x = (int) event.getX();
                        int y = (int) event.getY();
                        if (x >= progressX - AndroidUtilities.dp(10) && x <= progressX + AndroidUtilities.dp(10) && y >= progressY - AndroidUtilities.dp(10) && y <= progressY + AndroidUtilities.dp(10)) {
                            progressPressed = true;
                            lastProgressX = x;
                            currentProgressX = progressX;
                            getParent().requestDisallowInterceptTouchEvent(true);
                            invalidate();
                        }
                    }
                } else {
                    show(true, true);
                }
                AndroidUtilities.cancelRunOnUIThread(hideRunnable);
            } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
                if (initied && videoPlayer.isPlaying()) {
                    AndroidUtilities.runOnUIThread(hideRunnable, 3000);
                }
                if (progressPressed) {
                    progressPressed = false;
                    if (initied) {
                        progress = (int) (duration * ((float) (currentProgressX - progressLineX) / (progressLineEndX - progressLineX)));
                        videoPlayer.seekTo((long) progress * 1000);
                    }
                }
            } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
                if (progressPressed) {
                    int x = (int) event.getX();
                    currentProgressX -= (lastProgressX - x);
                    lastProgressX = x;
                    if (currentProgressX < progressLineX) {
                        currentProgressX = progressLineX;
                    } else if (currentProgressX > progressLineEndX) {
                        currentProgressX = progressLineEndX;
                    }
                    setProgress((int) (duration * 1000 * ((float) (currentProgressX - progressLineX) / (progressLineEndX - progressLineX))));
                    invalidate();
                }
            }
            super.onTouchEvent(event);
            return true;
        }

        @Override
        protected void onDraw(Canvas canvas) {
            if (drawImage) {
                if (firstFrameRendered && currentAlpha != 0) {
                    long newTime = System.currentTimeMillis();
                    long dt = newTime - lastUpdateTime;
                    lastUpdateTime = newTime;
                    currentAlpha -= dt / 150.0f;
                    if (currentAlpha < 0) {
                        currentAlpha = 0.0f;
                    }
                    invalidate();
                }
                imageReceiver.setAlpha(currentAlpha);
                imageReceiver.draw(canvas);
            }
            if (videoPlayer.isPlayerPrepared()) {
                int width = getMeasuredWidth();
                int height = getMeasuredHeight();
                if (!isInline) {
                    if (durationLayout != null) {
                        canvas.save();
                        canvas.translate(width - AndroidUtilities.dp(58) - durationWidth, height - AndroidUtilities.dp(29 + (inFullscreen ? 6 : 10)));
                        durationLayout.draw(canvas);
                        canvas.restore();
                    }

                    if (progressLayout != null) {
                        canvas.save();
                        canvas.translate(AndroidUtilities.dp(18), height - AndroidUtilities.dp(29 + (inFullscreen ? 6 : 10)));
                        progressLayout.draw(canvas);
                        canvas.restore();
                    }
                }

                if (duration != 0) {
                    int progressLineY;
                    int progressLineX;
                    int progressLineEndX;
                    int cy;

                    if (isInline) {
                        progressLineY = height - AndroidUtilities.dp(3);
                        progressLineX = 0;
                        progressLineEndX = width;
                        cy = height - AndroidUtilities.dp(7);
                    } else if (inFullscreen) {
                        progressLineY = height - AndroidUtilities.dp(26 + 3);
                        progressLineX = AndroidUtilities.dp(18 + 18) + durationWidth;
                        progressLineEndX = width - AndroidUtilities.dp(58 + 18) - durationWidth;
                        cy = height - AndroidUtilities.dp(7 + 21);
                    } else {
                        progressLineY = height - AndroidUtilities.dp(10 + 3);
                        progressLineX = 0;
                        progressLineEndX = width;
                        cy = height - AndroidUtilities.dp(2 + 10);
                    }
                    if (inFullscreen) {
                        canvas.drawRect(progressLineX, progressLineY, progressLineEndX, progressLineY + AndroidUtilities.dp(3), progressInnerPaint);
                    }
                    int progressX;
                    if (progressPressed) {
                        progressX = currentProgressX;
                    } else {
                        progressX = progressLineX + (int) ((progressLineEndX - progressLineX) * (progress / (float) duration));
                    }
                    if (bufferedPercentage != 0 && duration != 0) {
                        int pxPerS = (progressLineEndX - progressLineX) / duration;
                        int start = progressLineX + pxPerS * bufferedPosition;
                        int additional = 0;
                        if (progressX < start) {
                            additional = start - progressX;
                        }
                        canvas.drawRect(start - additional, progressLineY, start + (progressLineEndX - start) * bufferedPercentage / 100.0f, progressLineY + AndroidUtilities.dp(3), inFullscreen ? progressBufferedPaint : progressInnerPaint);
                    }
                    canvas.drawRect(progressLineX, progressLineY, progressX, progressLineY + AndroidUtilities.dp(3), progressPaint);
                    if (!isInline) {
                        canvas.drawCircle(progressX, cy, AndroidUtilities.dp(progressPressed ? 7 : 5), progressPaint);
                    }
                }
            }
        }
    }

    @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
    public WebPlayerView(Context context, boolean allowInline, boolean allowShare, WebPlayerViewDelegate webPlayerViewDelegate) {
        super(context);
        setWillNotDraw(false);
        delegate = webPlayerViewDelegate;

        backgroundPaint.setColor(0xff000000);

        aspectRatioFrameLayout = new AspectRatioFrameLayout(context) {
            @Override
            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                if (textureViewContainer != null) {
                    ViewGroup.LayoutParams layoutParams = textureView.getLayoutParams();
                    layoutParams.width = getMeasuredWidth();
                    layoutParams.height = getMeasuredHeight();

                    if (textureImageView != null) {
                        layoutParams = textureImageView.getLayoutParams();
                        layoutParams.width = getMeasuredWidth();
                        layoutParams.height = getMeasuredHeight();
                    }
                }
            }
        };
        addView(aspectRatioFrameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER));

        interfaceName = "JavaScriptInterface";
        webView = new WebView(context);
        webView.addJavascriptInterface(new JavaScriptInterface(new CallJavaResultInterface() {
            @Override
            public void jsCallFinished(String value) {
                if (currentTask != null && !currentTask.isCancelled()) {
                    if (currentTask instanceof YoutubeVideoTask) {
                        ((YoutubeVideoTask) currentTask).onInterfaceResult(value);
                    }
                }
            }
        }), interfaceName);
        final WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setDefaultTextEncodingName("utf-8");

        textureViewContainer = delegate.getTextureViewContainer();

        textureView = new TextureView(context);
        textureView.setPivotX(0);
        textureView.setPivotY(0);
        if (textureViewContainer != null) {
            textureViewContainer.addView(textureView);
        } else {
            aspectRatioFrameLayout.addView(textureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER));
        }

        if (allowInlineAnimation && textureViewContainer != null) {
            textureImageView = new ImageView(context);
            textureImageView.setBackgroundColor(0xffff0000);
            textureImageView.setPivotX(0);
            textureImageView.setPivotY(0);
            textureImageView.setVisibility(INVISIBLE);
            textureViewContainer.addView(textureImageView);
        }

        videoPlayer = new VideoPlayer();
        videoPlayer.setDelegate(this);
        videoPlayer.setTextureView(textureView);

        controlsView = new ControlsView(context);
        if (textureViewContainer != null) {
            textureViewContainer.addView(controlsView);
        } else {
            addView(controlsView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
        }

        progressView = new RadialProgressView(context);
        progressView.setProgressColor(0xffffffff);
        addView(progressView, LayoutHelper.createFrame(48, 48, Gravity.CENTER));

        fullscreenButton = new ImageView(context);
        fullscreenButton.setScaleType(ImageView.ScaleType.CENTER);
        controlsView.addView(fullscreenButton, LayoutHelper.createFrame(56, 56, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 0, 5));
        fullscreenButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!initied || changingTextureView || switchingInlineMode || !firstFrameRendered) {
                    return;
                }
                inFullscreen = !inFullscreen;
                updateFullscreenState(true);
            }
        });

        playButton = new ImageView(context);
        playButton.setScaleType(ImageView.ScaleType.CENTER);
        controlsView.addView(playButton, LayoutHelper.createFrame(48, 48, Gravity.CENTER));
        playButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!initied || playVideoUrl == null) {
                    return;
                }
                if (!videoPlayer.isPlayerPrepared()) {
                    preparePlayer();
                }
                if (videoPlayer.isPlaying()) {
                    videoPlayer.pause();
                } else {
                    isCompleted = false;
                    videoPlayer.play();
                }
                updatePlayButton();
            }
        });

        if (allowInline) {
            inlineButton = new ImageView(context);
            inlineButton.setScaleType(ImageView.ScaleType.CENTER);
            controlsView.addView(inlineButton, LayoutHelper.createFrame(56, 48, Gravity.RIGHT | Gravity.TOP));
            inlineButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (textureView == null || !delegate.checkInlinePermissons() || changingTextureView || switchingInlineMode || !firstFrameRendered) {
                        return;
                    }
                    switchingInlineMode = true;
                    if (!isInline) {
                        inFullscreen = false;
                        delegate.prepareToSwitchInlineMode(true, switchToInlineRunnable, aspectRatioFrameLayout.getAspectRatio(), allowInlineAnimation);
                    } else {
                        ViewGroup parent = (ViewGroup) aspectRatioFrameLayout.getParent();
                        if (parent != WebPlayerView.this) {
                            if (parent != null) {
                                parent.removeView(aspectRatioFrameLayout);
                            }
                            addView(aspectRatioFrameLayout, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER));
                            aspectRatioFrameLayout.measure(MeasureSpec.makeMeasureSpec(WebPlayerView.this.getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(WebPlayerView.this.getMeasuredHeight() - AndroidUtilities.dp(10), MeasureSpec.EXACTLY));
                        }
                        if (currentBitmap != null) {
                            currentBitmap.recycle();
                            currentBitmap = null;
                        }
                        changingTextureView = true;

                        isInline = false;
                        updatePlayButton();
                        updateShareButton();
                        updateFullscreenButton();
                        updateInlineButton();

                        textureView.setVisibility(INVISIBLE);
                        if (textureViewContainer != null) {
                            textureViewContainer.addView(textureView);
                        } else {
                            aspectRatioFrameLayout.addView(textureView);
                        }

                        parent = (ViewGroup) controlsView.getParent();
                        if (parent != WebPlayerView.this) {
                            if (parent != null) {
                                parent.removeView(controlsView);
                            }
                            if (textureViewContainer != null) {
                                textureViewContainer.addView(controlsView);
                            } else {
                                addView(controlsView, 1);
                            }
                        }

                        controlsView.show(false, false);
                        delegate.prepareToSwitchInlineMode(false, null, aspectRatioFrameLayout.getAspectRatio(), allowInlineAnimation);
                    }
                }
            });
        }

        if (allowShare) {
            shareButton = new ImageView(context);
            shareButton.setScaleType(ImageView.ScaleType.CENTER);
            shareButton.setImageResource(R.drawable.ic_share_video);
            controlsView.addView(shareButton, LayoutHelper.createFrame(56, 48, Gravity.RIGHT | Gravity.TOP));
            shareButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (delegate != null) {
                        delegate.onSharePressed();
                    }
                }
            });
        }

        updatePlayButton();
        updateFullscreenButton();
        updateInlineButton();
        updateShareButton();
    }

    private void onInitFailed() {
        if (controlsView.getParent() != this) {
            controlsView.setVisibility(GONE);
        }
        delegate.onInitFailed();
    }

    public void updateTextureImageView() {
        if (textureImageView == null) {
            return;
        }
        try {
            currentBitmap = Bitmaps.createBitmap(textureView.getWidth(), textureView.getHeight(), Bitmap.Config.ARGB_8888);
            changedTextureView.getBitmap(currentBitmap);
        } catch (Throwable e) {
            if (currentBitmap != null) {
                currentBitmap.recycle();
                currentBitmap = null;
            }
            FileLog.e(e);
        }
        if (currentBitmap != null) {
            textureImageView.setVisibility(VISIBLE);
            textureImageView.setImageBitmap(currentBitmap);
        } else {
            textureImageView.setImageDrawable(null);
        }
    }

    @Override
    public void onStateChanged(boolean playWhenReady, int playbackState) {
        if (playbackState != ExoPlayer.STATE_BUFFERING) {
            if (videoPlayer.getDuration() != C.TIME_UNSET) {
                controlsView.setDuration((int) (videoPlayer.getDuration() / 1000));
            } else {
                controlsView.setDuration(0);
            }
        }
        if (playbackState != ExoPlayer.STATE_ENDED && playbackState != ExoPlayer.STATE_IDLE && videoPlayer.isPlaying()) {
            delegate.onPlayStateChanged(this, true);
        } else {
            delegate.onPlayStateChanged(this, false);
        }
        if (videoPlayer.isPlaying() && playbackState != ExoPlayer.STATE_ENDED) {
            updatePlayButton();
        } else {
            if (playbackState == ExoPlayer.STATE_ENDED) {
                isCompleted = true;
                videoPlayer.pause();
                videoPlayer.seekTo(0);
                updatePlayButton();
                controlsView.show(true, true);
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight() - AndroidUtilities.dp(10), backgroundPaint);
    }

    @Override
    public void onError(Exception e) {
        FileLog.e(e);
    }

    @Override
    public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
        if (aspectRatioFrameLayout != null) {
            if (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270) {
                int temp = width;
                width = height;
                height = temp;
            }
            float ratio = height == 0 ? 1 : (width * pixelWidthHeightRatio) / height;
            aspectRatioFrameLayout.setAspectRatio(ratio, unappliedRotationDegrees);
            if (inFullscreen) {
                delegate.onVideoSizeChanged(ratio, unappliedRotationDegrees);
            }
        }
    }

    @Override
    public void onRenderedFirstFrame() {
        firstFrameRendered = true;
        lastUpdateTime = System.currentTimeMillis();
        controlsView.invalidate();
    }

    @Override
    public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) {
        if (changingTextureView) {
            changingTextureView = false;
            if (inFullscreen || isInline) {
                if (isInline) {
                    waitingForFirstTextureUpload = 1;
                }
                changedTextureView.setSurfaceTexture(surfaceTexture);
                changedTextureView.setSurfaceTextureListener(surfaceTextureListener);
                changedTextureView.setVisibility(VISIBLE);
                return true;
            }
        }
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
        if (waitingForFirstTextureUpload == 2) {
            if (textureImageView != null) {
                textureImageView.setVisibility(INVISIBLE);
                textureImageView.setImageDrawable(null);
                if (currentBitmap != null) {
                    currentBitmap.recycle();
                    currentBitmap = null;
                }
            }
            switchingInlineMode = false;
            delegate.onSwitchInlineMode(controlsView, false, aspectRatioFrameLayout.getAspectRatio(), aspectRatioFrameLayout.getVideoRotation(), allowInlineAnimation);
            waitingForFirstTextureUpload = 0;
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int x = ((r - l) - aspectRatioFrameLayout.getMeasuredWidth()) / 2;
        int y = ((b - t - AndroidUtilities.dp(10)) - aspectRatioFrameLayout.getMeasuredHeight()) / 2;
        aspectRatioFrameLayout.layout(x, y, x + aspectRatioFrameLayout.getMeasuredWidth(), y + aspectRatioFrameLayout.getMeasuredHeight());
        if (controlsView.getParent() == this) {
            controlsView.layout(0, 0, controlsView.getMeasuredWidth(), controlsView.getMeasuredHeight());
        }
        x = ((r - l) - progressView.getMeasuredWidth()) / 2;
        y = ((b - t) - progressView.getMeasuredHeight()) / 2;
        progressView.layout(x, y, x + progressView.getMeasuredWidth(), y + progressView.getMeasuredHeight());
        controlsView.imageReceiver.setImageCoords(0, 0, getMeasuredWidth(), getMeasuredHeight() - AndroidUtilities.dp(10));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        aspectRatioFrameLayout.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height - AndroidUtilities.dp(10), MeasureSpec.EXACTLY));
        if (controlsView.getParent() == this) {
            controlsView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
        }
        progressView.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(44), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(44), MeasureSpec.EXACTLY));
        setMeasuredDimension(width, height);
    }

    private void updatePlayButton() {
        controlsView.checkNeedHide();
        AndroidUtilities.cancelRunOnUIThread(progressRunnable);
        if (!videoPlayer.isPlaying()) {
            if (isCompleted) {
                playButton.setImageResource(isInline ? R.drawable.ic_againinline : R.drawable.ic_again);
            } else {
                playButton.setImageResource(isInline ? R.drawable.ic_playinline : R.drawable.ic_play);
            }
        } else {
            playButton.setImageResource(isInline ? R.drawable.ic_pauseinline : R.drawable.ic_pause);
            AndroidUtilities.runOnUIThread(progressRunnable, 500);
            checkAudioFocus();
        }
    }

    private void checkAudioFocus() {
        if (!hasAudioFocus) {
            AudioManager audioManager = (AudioManager) ApplicationLoader.applicationContext.getSystemService(Context.AUDIO_SERVICE);
            hasAudioFocus = true;
            if (audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
                audioFocus = 2;
            }
        }
    }

    @Override
    public void onAudioFocusChange(int focusChange) {
        if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
            if (videoPlayer.isPlaying()) {
                videoPlayer.pause();
                updatePlayButton();
            }
            hasAudioFocus = false;
            audioFocus = AUDIO_NO_FOCUS_NO_DUCK;
        } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
            audioFocus = AUDIO_FOCUSED;
            if (resumeAudioOnFocusGain) {
                resumeAudioOnFocusGain = false;
                videoPlayer.play();
            }
        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
            audioFocus = AUDIO_NO_FOCUS_CAN_DUCK;
        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
            audioFocus = AUDIO_NO_FOCUS_NO_DUCK;
            if (videoPlayer.isPlaying()) {
                resumeAudioOnFocusGain = true;
                videoPlayer.pause();
                updatePlayButton();
            }
        }
    }

    private void updateFullscreenButton() {
        if (!videoPlayer.isPlayerPrepared() || isInline) {
            fullscreenButton.setVisibility(GONE);
            return;
        }
        fullscreenButton.setVisibility(VISIBLE);
        if (!inFullscreen) {
            fullscreenButton.setImageResource(R.drawable.ic_gofullscreen);
            fullscreenButton.setLayoutParams(LayoutHelper.createFrame(56, 56, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 0, 5));
        } else {
            fullscreenButton.setImageResource(R.drawable.ic_outfullscreen);
            fullscreenButton.setLayoutParams(LayoutHelper.createFrame(56, 56, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 0, 1));
        }
    }

    private void updateShareButton() {
        if (shareButton == null) {
            return;
        }
        shareButton.setVisibility(isInline || !videoPlayer.isPlayerPrepared() ? GONE : VISIBLE);
    }

    private View getControlView() {
        return controlsView;
    }

    private View getProgressView() {
        return progressView;
    }

    private void updateInlineButton() {
        if (inlineButton == null) {
            return;
        }
        inlineButton.setImageResource(isInline ? R.drawable.ic_goinline : R.drawable.ic_outinline);
        inlineButton.setVisibility(videoPlayer.isPlayerPrepared() ? VISIBLE : GONE);
        if (isInline) {
            inlineButton.setLayoutParams(LayoutHelper.createFrame(40, 40, Gravity.RIGHT | Gravity.TOP));
        } else {
            inlineButton.setLayoutParams(LayoutHelper.createFrame(56, 50, Gravity.RIGHT | Gravity.TOP));
        }
    }

    private void preparePlayer() {
        if (playVideoUrl == null) {
            return;
        }
        if (playVideoUrl != null && playAudioUrl != null) {
            videoPlayer.preparePlayerLoop(Uri.parse(playVideoUrl), playVideoType, Uri.parse(playAudioUrl), playAudioType);
        } else {
            videoPlayer.preparePlayer(Uri.parse(playVideoUrl), playVideoType);
        }
        videoPlayer.setPlayWhenReady(isAutoplay);

        isLoading = false;

        if (videoPlayer.getDuration() != C.TIME_UNSET) {
            controlsView.setDuration((int) (videoPlayer.getDuration() / 1000));
        } else {
            controlsView.setDuration(0);
        }
        updateFullscreenButton();
        updateShareButton();
        updateInlineButton();
        controlsView.invalidate();
        if (seekToTime != -1) {
            videoPlayer.seekTo(seekToTime * 1000);
        }
    }

    public void pause() {
        videoPlayer.pause();
        updatePlayButton();
        controlsView.show(true, true);
    }

    private void updateFullscreenState(boolean byButton) {
        if (textureView == null) {
            return;
        }
        updateFullscreenButton();
        if (textureViewContainer == null) {
            changingTextureView = true;
            if (!inFullscreen) {
                if (textureViewContainer != null) {
                    textureViewContainer.addView(textureView);
                } else {
                    aspectRatioFrameLayout.addView(textureView);
                }
            }
            if (inFullscreen) {
                ViewGroup viewGroup = (ViewGroup) controlsView.getParent();
                if (viewGroup != null) {
                    viewGroup.removeView(controlsView);
                }
            } else {
                ViewGroup parent = (ViewGroup) controlsView.getParent();
                if (parent != this) {
                    if (parent != null) {
                        parent.removeView(controlsView);
                    }
                    if (textureViewContainer != null) {
                        textureViewContainer.addView(controlsView);
                    } else {
                        addView(controlsView, 1);
                    }
                }
            }
            changedTextureView = delegate.onSwitchToFullscreen(controlsView, inFullscreen, aspectRatioFrameLayout.getAspectRatio(), aspectRatioFrameLayout.getVideoRotation(), byButton);
            changedTextureView.setVisibility(INVISIBLE);
            if (inFullscreen && changedTextureView != null) {
                ViewGroup parent = (ViewGroup) textureView.getParent();
                if (parent != null) {
                    parent.removeView(textureView);
                }
            }
            controlsView.checkNeedHide();
        } else {
            if (inFullscreen) {
                ViewGroup viewGroup = (ViewGroup) aspectRatioFrameLayout.getParent();
                if (viewGroup != null) {
                    viewGroup.removeView(aspectRatioFrameLayout);
                }
            } else {
                ViewGroup parent = (ViewGroup) aspectRatioFrameLayout.getParent();
                if (parent != this) {
                    if (parent != null) {
                        parent.removeView(aspectRatioFrameLayout);
                    }
                    addView(aspectRatioFrameLayout, 0);
                }
            }
            delegate.onSwitchToFullscreen(controlsView, inFullscreen, aspectRatioFrameLayout.getAspectRatio(), aspectRatioFrameLayout.getVideoRotation(), byButton);
        }
    }

    public void exitFullscreen() {
        if (!inFullscreen) {
            return;
        }
        inFullscreen = false;
        updateInlineButton();
        updateFullscreenState(false);
    }

    public boolean isInitied() {
        return initied;
    }

    public boolean isInline() {
        return isInline || switchingInlineMode;
    }

    public void enterFullscreen() {
        if (inFullscreen) {
            return;
        }
        inFullscreen = true;
        updateInlineButton();
        updateFullscreenState(false);
    }

    public boolean isInFullscreen() {
        return inFullscreen;
    }

    public boolean loadVideo(String url, TLRPC.Photo thumb, String originalUrl, boolean autoplay) {
        String youtubeId = null;
        String vimeoId = null;
        String coubId = null;
        String aparatId = null;
        String mp4File = null;
        seekToTime = -1;
        if (url != null) {
            if (url.endsWith(".mp4")) {
                mp4File = url;
            } else {
                try {
                    if (originalUrl != null) {
                        try {
                            Uri uri = Uri.parse(originalUrl);
                            String t = uri.getQueryParameter("t");
                            if (t != null) {
                                if (t.contains("m")) {
                                    String args[] = t.split("m");
                                    seekToTime = Utilities.parseInt(args[0]) * 60 + Utilities.parseInt(args[1]);
                                } else {
                                    seekToTime = Utilities.parseInt(t);
                                }
                            }
                        } catch (Exception e) {
                            FileLog.e(e);
                        }
                    }
                    Matcher matcher = youtubeIdRegex.matcher(url);
                    String id = null;
                    if (matcher.find()) {
                        id = matcher.group(1);
                    }
                    if (id != null) {
                        youtubeId = id;
                    }
                } catch (Exception e) {
                    FileLog.e(e);
                }
                if (youtubeId == null) {
                    try {
                        Matcher matcher = vimeoIdRegex.matcher(url);
                        String id = null;
                        if (matcher.find()) {
                            id = matcher.group(3);
                        }
                        if (id != null) {
                            vimeoId = id;
                        }
                    } catch (Exception e) {
                        FileLog.e(e);
                    }
                }
                if (youtubeId == null && vimeoId == null) {
                    try {
                        Matcher matcher = aparatIdRegex.matcher(url);
                        String id = null;
                        if (matcher.find()) {
                            id = matcher.group(1);
                        }
                        if (id != null) {
                            aparatId = id;
                        }
                    } catch (Exception e) {
                        FileLog.e(e);
                    }
                }
                /*if (youtubeId == null && vimeoId == null) {
                    try {
                        Matcher matcher = coubIdRegex.matcher(url);
                        String id = null;
                        if (matcher.find()) {
                            id = matcher.group(1);
                        }
                        if (id != null) {
                            coubId = id;
                        }
                    } catch (Exception e) {
                        FileLog.e(e);
                    }
                }*/
            }
        }

        initied = false;
        isCompleted = false;
        isAutoplay = autoplay;
        playVideoUrl = null;
        playAudioUrl = null;
        destroy();
        firstFrameRendered = false;
        currentAlpha = 1.0f;
        if (currentTask != null) {
            currentTask.cancel(true);
            currentTask = null;
        }
        updateFullscreenButton();
        updateShareButton();
        updateInlineButton();
        updatePlayButton();
        if (thumb != null) {
            TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(thumb.sizes, 80, true);
            if (photoSize != null) {
                controlsView.imageReceiver.setImage(null, null, thumb != null ? photoSize.location : null, thumb != null ? "80_80_b" : null, 0, null, 1);
                drawImage = true;
            }
        } else {
            drawImage = false;
        }

        if (progressAnimation != null) {
            progressAnimation.cancel();
            progressAnimation = null;
        }
        isLoading = true;
        controlsView.setProgress(0);
        if (mp4File != null) {
            initied = true;
            playVideoUrl = mp4File;
            playVideoType = "other";
            if (isAutoplay) {
                preparePlayer();
            }
            showProgress(false, false);
            controlsView.show(true, true);
        } else {
            if (youtubeId != null) {
                YoutubeVideoTask task = new YoutubeVideoTask(youtubeId);
                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
                currentTask = task;
            } else if (vimeoId != null) {
                VimeoVideoTask task = new VimeoVideoTask(vimeoId);
                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
                currentTask = task;
            } else if (coubId != null) {
                CoubVideoTask task = new CoubVideoTask(coubId);
                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
                currentTask = task;
            } else if (aparatId != null) {
                AparatVideoTask task = new AparatVideoTask(aparatId);
                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null);
                currentTask = task;
            }

            controlsView.show(false, false);
            showProgress(true, false);
        }
        if (youtubeId != null || vimeoId != null || coubId != null || aparatId != null || mp4File != null) {
            return true;
        }
        controlsView.setVisibility(GONE);
        return false;
    }

    public View getAspectRatioView() {
        return aspectRatioFrameLayout;
    }

    public TextureView getTextureView() {
        return textureView;
    }

    public ImageView getTextureImageView() {
        return textureImageView;
    }

    public View getControlsView() {
        return controlsView;
    }

    public void destroy() {
        videoPlayer.releasePlayer();
        if (currentTask != null) {
            currentTask.cancel(true);
            currentTask = null;
        }
        webView.stopLoading();
    }

    private void showProgress(boolean show, boolean animated) {
        if (animated) {
            if (progressAnimation != null) {
                progressAnimation.cancel();
            }
            progressAnimation = new AnimatorSet();
            progressAnimation.playTogether(ObjectAnimator.ofFloat(progressView, "alpha", show ? 1.0f : 0.0f));
            progressAnimation.setDuration(150);
            progressAnimation.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animator) {
                    progressAnimation = null;
                }
            });
            progressAnimation.start();
        } else {
            progressView.setAlpha(show ? 1.0f : 0.0f);
        }
    }
}