mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-11-04 09:13:00 +00:00 
			
		
		
		
	finished implementing timestamp, along with refactoring services
* added VideoInfo(AbstractVideoInfo) constructor, to support later implementation for reusing info scraped into VideoPreviewInfo, into VideoInfo
* Made the Extractor class behave as a per-video object;
    - most method return values are video-specific, so it makes sense (to me) to have Extractor be stateful.
    - The only stateless methods are getVideoUrl(), getVideoId() and loadDecryptionCode(String)
* Implemented a constructor for YoutubeExtractor, which performs all initialisation work
			
			
This commit is contained in:
		@@ -7,9 +7,10 @@ public abstract class AbstractVideoInfo {
 | 
				
			|||||||
    public String id = "";
 | 
					    public String id = "";
 | 
				
			||||||
    public String title = "";
 | 
					    public String title = "";
 | 
				
			||||||
    public String uploader = "";
 | 
					    public String uploader = "";
 | 
				
			||||||
 | 
					    //public int duration = -1;
 | 
				
			||||||
    public String thumbnail_url = "";
 | 
					    public String thumbnail_url = "";
 | 
				
			||||||
    public Bitmap thumbnail = null;
 | 
					    public Bitmap thumbnail = null;
 | 
				
			||||||
    public String webpage_url = "";
 | 
					    public String webpage_url = "";
 | 
				
			||||||
    public String upload_date = "";
 | 
					    public String upload_date = "";
 | 
				
			||||||
    public long view_count = 0;
 | 
					    public long view_count = -1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,44 +24,76 @@ import android.graphics.Bitmap;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**Info object for opened videos, ie the video ready to play.*/
 | 
					/**Info object for opened videos, ie the video ready to play.*/
 | 
				
			||||||
public class VideoInfo extends AbstractVideoInfo {
 | 
					public class VideoInfo extends AbstractVideoInfo {
 | 
				
			||||||
 | 
					    private static final String TAG = VideoInfo.class.toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public String uploader_thumbnail_url = "";
 | 
					    public String uploader_thumbnail_url = "";
 | 
				
			||||||
    public Bitmap uploader_thumbnail = null;
 | 
					    public Bitmap uploader_thumbnail = null;
 | 
				
			||||||
    public String description = "";
 | 
					    public String description = "";
 | 
				
			||||||
    public int duration = -1;
 | 
					 | 
				
			||||||
    public int age_limit = 0;
 | 
					 | 
				
			||||||
    public int like_count = 0;
 | 
					 | 
				
			||||||
    public int dislike_count = 0;
 | 
					 | 
				
			||||||
    public String average_rating = "";
 | 
					 | 
				
			||||||
    public VideoStream[] videoStreams = null;
 | 
					    public VideoStream[] videoStreams = null;
 | 
				
			||||||
    public AudioStream[] audioStreams = null;
 | 
					    public AudioStream[] audioStreams = null;
 | 
				
			||||||
 | 
					    public int videoAvailableStatus = VIDEO_AVAILABLE;
 | 
				
			||||||
 | 
					    public int duration = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /*YouTube-specific fields
 | 
				
			||||||
 | 
					    todo: move these to a subclass*/
 | 
				
			||||||
 | 
					    public int age_limit = 0;
 | 
				
			||||||
 | 
					    public int like_count = -1;
 | 
				
			||||||
 | 
					    public int dislike_count = -1;
 | 
				
			||||||
 | 
					    public String average_rating = "";
 | 
				
			||||||
    public VideoPreviewInfo nextVideo = null;
 | 
					    public VideoPreviewInfo nextVideo = null;
 | 
				
			||||||
    public VideoPreviewInfo[] relatedVideos = null;
 | 
					    public VideoPreviewInfo[] relatedVideos = null;
 | 
				
			||||||
    public int videoAvailableStatus = VIDEO_AVAILABLE;
 | 
					    public int startPosition = -1;//in seconds. some metadata is not passed using a VideoInfo object!
 | 
				
			||||||
    //public int startPosition = 0;//in seconds. some metadata is not passed using a VideoInfo object!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static final String TAG = VideoInfo.class.toString();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static final int VIDEO_AVAILABLE = 0x00;
 | 
					    public static final int VIDEO_AVAILABLE = 0x00;
 | 
				
			||||||
    public static final int VIDEO_UNAVAILABLE = 0x01;
 | 
					    public static final int VIDEO_UNAVAILABLE = 0x01;
 | 
				
			||||||
    public static final int VIDEO_UNAVAILABLE_GEMA = 0x02;//German DRM organisation
 | 
					    public static final int VIDEO_UNAVAILABLE_GEMA = 0x02;//German DRM organisation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static class VideoStream {
 | 
					
 | 
				
			||||||
        public VideoStream(String url, int format, String res) {
 | 
					    public VideoInfo() {}
 | 
				
			||||||
            this.url = url; this.format = format; resolution = res;
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**Creates a new VideoInfo object from an existing AbstractVideoInfo.
 | 
				
			||||||
 | 
					     * All the shared properties are copied to the new VideoInfo.*/
 | 
				
			||||||
 | 
					    public VideoInfo(AbstractVideoInfo avi) {
 | 
				
			||||||
 | 
					        this.id = avi.id;
 | 
				
			||||||
 | 
					        this.title = avi.title;
 | 
				
			||||||
 | 
					        this.uploader = avi.uploader;
 | 
				
			||||||
 | 
					        this.thumbnail_url = avi.thumbnail_url;
 | 
				
			||||||
 | 
					        this.thumbnail = avi.thumbnail;
 | 
				
			||||||
 | 
					        this.webpage_url = avi.webpage_url;
 | 
				
			||||||
 | 
					        this.upload_date = avi.upload_date;
 | 
				
			||||||
 | 
					        this.upload_date = avi.upload_date;
 | 
				
			||||||
 | 
					        this.view_count = avi.view_count;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //todo: better than this
 | 
				
			||||||
 | 
					        if(avi instanceof VideoPreviewInfo) {//shitty String to convert code
 | 
				
			||||||
 | 
					            String dur = ((VideoPreviewInfo)avi).duration;
 | 
				
			||||||
 | 
					            int minutes = Integer.parseInt(dur.substring(0, dur.indexOf(":")));
 | 
				
			||||||
 | 
					            int seconds = Integer.parseInt(dur.substring(dur.indexOf(":")+1, dur.length()));
 | 
				
			||||||
 | 
					            this.duration = (minutes*60)+seconds;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static class VideoStream {
 | 
				
			||||||
        public String url = "";     //url of the stream
 | 
					        public String url = "";     //url of the stream
 | 
				
			||||||
        public int format = -1;
 | 
					        public int format = -1;
 | 
				
			||||||
        public String resolution = "";
 | 
					        public String resolution = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public VideoStream(String url, int format, String res) {
 | 
				
			||||||
 | 
					            this.url = url; this.format = format; resolution = res;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static class AudioStream {
 | 
					    public static class AudioStream {
 | 
				
			||||||
        public AudioStream(String url, int format, int bandwidth, int samplingRate) {
 | 
					 | 
				
			||||||
            this.url = url; this.format = format;
 | 
					 | 
				
			||||||
            this.bandwidth = bandwidth; this.samplingRate = samplingRate;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        public String url = "";
 | 
					        public String url = "";
 | 
				
			||||||
        public int format = -1;
 | 
					        public int format = -1;
 | 
				
			||||||
        public int bandwidth = -1;
 | 
					        public int bandwidth = -1;
 | 
				
			||||||
        public int samplingRate = -1;
 | 
					        public int samplingRate = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public AudioStream(String url, int format, int bandwidth, int samplingRate) {
 | 
				
			||||||
 | 
					            this.url = url; this.format = format;
 | 
				
			||||||
 | 
					            this.bandwidth = bandwidth; this.samplingRate = samplingRate;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -57,7 +57,7 @@ public class VideoInfoItemViewCreator {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        holder.itemVideoTitleView.setText(info.title);
 | 
					        holder.itemVideoTitleView.setText(info.title);
 | 
				
			||||||
        holder.itemUploaderView.setText(info.uploader);
 | 
					        holder.itemUploaderView.setText(info.uploader);
 | 
				
			||||||
        holder.itemDurationView.setText(info.duration);
 | 
					        holder.itemDurationView.setText(""+info.duration);
 | 
				
			||||||
        if(!info.upload_date.isEmpty()) {
 | 
					        if(!info.upload_date.isEmpty()) {
 | 
				
			||||||
            holder.itemUploadDateView.setText(info.upload_date);
 | 
					            holder.itemUploadDateView.setText(info.upload_date);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,27 +64,25 @@ public class VideoItemDetailActivity extends AppCompatActivity {
 | 
				
			|||||||
            // this means the video was called though another app
 | 
					            // this means the video was called though another app
 | 
				
			||||||
            if (getIntent().getData() != null) {
 | 
					            if (getIntent().getData() != null) {
 | 
				
			||||||
                videoUrl = getIntent().getData().toString();
 | 
					                videoUrl = getIntent().getData().toString();
 | 
				
			||||||
                Log.i(TAG, "video URL passed:\"" + videoUrl + "\"");
 | 
					                //Log.i(TAG, "video URL passed:\"" + videoUrl + "\"");
 | 
				
			||||||
                StreamingService[] serviceList = ServiceList.getServices();
 | 
					                StreamingService[] serviceList = ServiceList.getServices();
 | 
				
			||||||
                Extractor extractor = null;
 | 
					                Extractor extractor = null;
 | 
				
			||||||
                for (int i = 0; i < serviceList.length; i++) {
 | 
					                for (int i = 0; i < serviceList.length; i++) {
 | 
				
			||||||
                    if (serviceList[i].acceptUrl(videoUrl)) {
 | 
					                    if (serviceList[i].acceptUrl(videoUrl)) {
 | 
				
			||||||
                        arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, i);
 | 
					                        arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, i);
 | 
				
			||||||
                        try {
 | 
					                        currentStreamingService = i;
 | 
				
			||||||
                            currentStreamingService = i;
 | 
					                        //extractor = ServiceList.getService(i).getExtractorInstance();
 | 
				
			||||||
                            extractor = ServiceList.getService(i).getExtractorInstance();
 | 
					 | 
				
			||||||
                        } catch (Exception e) {
 | 
					 | 
				
			||||||
                            e.printStackTrace();
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        break;
 | 
					                        break;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if(extractor == null) {
 | 
					                if(currentStreamingService == -1) {
 | 
				
			||||||
                    Toast.makeText(this, R.string.urlNotSupportedText, Toast.LENGTH_LONG)
 | 
					                    Toast.makeText(this, R.string.urlNotSupportedText, Toast.LENGTH_LONG)
 | 
				
			||||||
                            .show();
 | 
					                            .show();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                arguments.putString(VideoItemDetailFragment.VIDEO_URL,
 | 
					                //arguments.putString(VideoItemDetailFragment.VIDEO_URL,
 | 
				
			||||||
                        extractor.getVideoUrl(extractor.getVideoId(videoUrl)));//cleans URL
 | 
					                //        extractor.getVideoUrl(extractor.getVideoId(videoUrl)));//cleans URL
 | 
				
			||||||
 | 
					                arguments.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY,
 | 
					                arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY,
 | 
				
			||||||
                        PreferenceManager.getDefaultSharedPreferences(this)
 | 
					                        PreferenceManager.getDefaultSharedPreferences(this)
 | 
				
			||||||
                                .getBoolean(getString(R.string.autoPlayThroughIntent), false));
 | 
					                                .getBoolean(getString(R.string.autoPlayThroughIntent), false));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -90,16 +90,18 @@ public class VideoItemDetailFragment extends Fragment {
 | 
				
			|||||||
    private class ExtractorRunnable implements Runnable {
 | 
					    private class ExtractorRunnable implements Runnable {
 | 
				
			||||||
        private Handler h = new Handler();
 | 
					        private Handler h = new Handler();
 | 
				
			||||||
        private Extractor extractor;
 | 
					        private Extractor extractor;
 | 
				
			||||||
 | 
					        private StreamingService service;
 | 
				
			||||||
        private String videoUrl;
 | 
					        private String videoUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public ExtractorRunnable(String videoUrl, Extractor extractor, VideoItemDetailFragment f) {
 | 
					        public ExtractorRunnable(String videoUrl, StreamingService service, VideoItemDetailFragment f) {
 | 
				
			||||||
            this.extractor = extractor;
 | 
					            this.service = service;
 | 
				
			||||||
            this.videoUrl = videoUrl;
 | 
					            this.videoUrl = videoUrl;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public void run() {
 | 
					        public void run() {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                VideoInfo videoInfo = extractor.getVideoInfo(videoUrl);
 | 
					                this.extractor = service.getExtractorInstance(videoUrl);
 | 
				
			||||||
 | 
					                VideoInfo videoInfo = extractor.getVideoInfo();
 | 
				
			||||||
                h.post(new VideoResultReturnedRunnable(videoInfo));
 | 
					                h.post(new VideoResultReturnedRunnable(videoInfo));
 | 
				
			||||||
                if (videoInfo.videoAvailableStatus == VideoInfo.VIDEO_AVAILABLE) {
 | 
					                if (videoInfo.videoAvailableStatus == VideoInfo.VIDEO_AVAILABLE) {
 | 
				
			||||||
                    h.post(new SetThumbnailRunnable(
 | 
					                    h.post(new SetThumbnailRunnable(
 | 
				
			||||||
@@ -239,7 +241,7 @@ public class VideoItemDetailFragment extends Fragment {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    //this is horribly convoluted
 | 
					                    //this is horribly convoluted
 | 
				
			||||||
                    //TODO: find a better way to convert YYYY-MM-DD to a locale-specific date
 | 
					                    //TODO: find a better way to convert YYYY-MM-DD to a locale-specific date
 | 
				
			||||||
                    //suggestions welcome
 | 
					                    //suggestions are welcome
 | 
				
			||||||
                    int year  = Integer.parseInt(info.upload_date.substring(0, 4));
 | 
					                    int year  = Integer.parseInt(info.upload_date.substring(0, 4));
 | 
				
			||||||
                    int month = Integer.parseInt(info.upload_date.substring(5, 7));
 | 
					                    int month = Integer.parseInt(info.upload_date.substring(5, 7));
 | 
				
			||||||
                    int date  = Integer.parseInt(info.upload_date.substring(8, 10));
 | 
					                    int date  = Integer.parseInt(info.upload_date.substring(8, 10));
 | 
				
			||||||
@@ -255,6 +257,7 @@ public class VideoItemDetailFragment extends Fragment {
 | 
				
			|||||||
                    descriptionView.setMovementMethod(LinkMovementMethod.getInstance());
 | 
					                    descriptionView.setMovementMethod(LinkMovementMethod.getInstance());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    actionBarHandler.setVideoInfo(info.webpage_url, info.title);
 | 
					                    actionBarHandler.setVideoInfo(info.webpage_url, info.title);
 | 
				
			||||||
 | 
					                    actionBarHandler.setStartPosition(info.startPosition);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    // parse streams
 | 
					                    // parse streams
 | 
				
			||||||
                    Vector<VideoInfo.VideoStream> streamsToUse = new Vector<>();
 | 
					                    Vector<VideoInfo.VideoStream> streamsToUse = new Vector<>();
 | 
				
			||||||
@@ -357,7 +360,7 @@ public class VideoItemDetailFragment extends Fragment {
 | 
				
			|||||||
                StreamingService streamingService = ServiceList.getService(
 | 
					                StreamingService streamingService = ServiceList.getService(
 | 
				
			||||||
                        getArguments().getInt(STREAMING_SERVICE));
 | 
					                        getArguments().getInt(STREAMING_SERVICE));
 | 
				
			||||||
                extractorThread = new Thread(new ExtractorRunnable(
 | 
					                extractorThread = new Thread(new ExtractorRunnable(
 | 
				
			||||||
                        getArguments().getString(VIDEO_URL), streamingService.getExtractorInstance(), this));
 | 
					                        getArguments().getString(VIDEO_URL), streamingService, this));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
 | 
					                autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
 | 
				
			||||||
                extractorThread.start();
 | 
					                extractorThread.start();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,10 +26,7 @@ import android.os.Parcelable;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**Info object for previews of unopened videos, eg search results, related videos*/
 | 
					/**Info object for previews of unopened videos, eg search results, related videos*/
 | 
				
			||||||
public class VideoPreviewInfo extends AbstractVideoInfo implements Parcelable {
 | 
					public class VideoPreviewInfo extends AbstractVideoInfo implements Parcelable {
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public String duration = "";
 | 
					    public String duration = "";
 | 
				
			||||||
 | 
					 | 
				
			||||||
    protected VideoPreviewInfo(Parcel in) {
 | 
					    protected VideoPreviewInfo(Parcel in) {
 | 
				
			||||||
        id = in.readString();
 | 
					        id = in.readString();
 | 
				
			||||||
        title = in.readString();
 | 
					        title = in.readString();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,9 +22,94 @@ package org.schabi.newpipe.services;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import org.schabi.newpipe.VideoInfo;
 | 
					import org.schabi.newpipe.VideoInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**Scrapes information from a video streaming service (eg, YouTube). To implement*/
 | 
					/**Scrapes information from a video streaming service (eg, YouTube).*/
 | 
				
			||||||
public interface Extractor {
 | 
					public abstract class Extractor {
 | 
				
			||||||
    VideoInfo getVideoInfo(String siteUrl);
 | 
					    public String pageUrl;
 | 
				
			||||||
    String getVideoUrl(String videoId);
 | 
					    public VideoInfo videoInfo;
 | 
				
			||||||
    String getVideoId(String videoUrl);
 | 
					
 | 
				
			||||||
 | 
					    public Extractor(String url) {
 | 
				
			||||||
 | 
					        this.pageUrl = url;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**Fills out the video info fields which are common to all services.
 | 
				
			||||||
 | 
					     * Probably needs to be overridden by subclasses*/
 | 
				
			||||||
 | 
					    public VideoInfo getVideoInfo()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if(videoInfo == null) {
 | 
				
			||||||
 | 
					            videoInfo = new VideoInfo();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(videoInfo.webpage_url.isEmpty()) {
 | 
				
			||||||
 | 
					            videoInfo.webpage_url = pageUrl;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(videoInfo.title.isEmpty()) {
 | 
				
			||||||
 | 
					            videoInfo.title = getTitle();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(videoInfo.duration  < 1) {
 | 
				
			||||||
 | 
					            videoInfo.duration = getLength();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(videoInfo.uploader.isEmpty()) {
 | 
				
			||||||
 | 
					            videoInfo.uploader = getUploader();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(videoInfo.description.isEmpty()) {
 | 
				
			||||||
 | 
					            videoInfo.description = getDescription();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(videoInfo.view_count == -1) {
 | 
				
			||||||
 | 
					            videoInfo.view_count = getViews();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(videoInfo.upload_date.isEmpty()) {
 | 
				
			||||||
 | 
					            videoInfo.upload_date = getUploadDate();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(videoInfo.thumbnail_url.isEmpty()) {
 | 
				
			||||||
 | 
					            videoInfo.thumbnail_url = getThumbnailUrl();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(videoInfo.id.isEmpty()) {
 | 
				
			||||||
 | 
					            videoInfo.id = getVideoId(pageUrl);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** Load and extract audio*/
 | 
				
			||||||
 | 
					        if(videoInfo.audioStreams == null) {
 | 
				
			||||||
 | 
					            videoInfo.audioStreams = getAudioStreams();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        /** Extract video stream url*/
 | 
				
			||||||
 | 
					        if(videoInfo.videoStreams == null) {
 | 
				
			||||||
 | 
					            videoInfo.videoStreams = getVideoStreams();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(videoInfo.uploader_thumbnail_url.isEmpty()) {
 | 
				
			||||||
 | 
					            videoInfo.uploader_thumbnail_url = getUploaderThumbnailUrl();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(videoInfo.startPosition < 0) {
 | 
				
			||||||
 | 
					            videoInfo.startPosition = getTimeStamp();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Bitmap thumbnail = null;
 | 
				
			||||||
 | 
					        //Bitmap uploader_thumbnail = null;
 | 
				
			||||||
 | 
					        //int videoAvailableStatus = VIDEO_AVAILABLE;
 | 
				
			||||||
 | 
					        return videoInfo;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public abstract String getVideoUrl(String videoId);
 | 
				
			||||||
 | 
					    public abstract String getVideoId(String siteUrl);
 | 
				
			||||||
 | 
					    public abstract int getTimeStamp();
 | 
				
			||||||
 | 
					    public abstract String getTitle();
 | 
				
			||||||
 | 
					    public abstract String getDescription();
 | 
				
			||||||
 | 
					    public abstract String getUploader();
 | 
				
			||||||
 | 
					    public abstract int getLength();
 | 
				
			||||||
 | 
					    public abstract int getViews();
 | 
				
			||||||
 | 
					    public abstract String getUploadDate();
 | 
				
			||||||
 | 
					    public abstract String getThumbnailUrl();
 | 
				
			||||||
 | 
					    public abstract String getUploaderThumbnailUrl();
 | 
				
			||||||
 | 
					    public abstract VideoInfo.AudioStream[] getAudioStreams();
 | 
				
			||||||
 | 
					    public abstract VideoInfo.VideoStream[] getVideoStreams();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@ public interface StreamingService {
 | 
				
			|||||||
        public String name = "";
 | 
					        public String name = "";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    ServiceInfo getServiceInfo();
 | 
					    ServiceInfo getServiceInfo();
 | 
				
			||||||
    Extractor getExtractorInstance();
 | 
					    Extractor getExtractorInstance(String url);
 | 
				
			||||||
    SearchEngine getSearchEngineInstance();
 | 
					    SearchEngine getSearchEngineInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**When a VIEW_ACTION is caught this function will test if the url delivered within the calling
 | 
					    /**When a VIEW_ACTION is caught this function will test if the url delivered within the calling
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ package org.schabi.newpipe.services.youtube;
 | 
				
			|||||||
import android.util.Log;
 | 
					import android.util.Log;
 | 
				
			||||||
import android.util.Xml;
 | 
					import android.util.Xml;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.json.JSONException;
 | 
				
			||||||
import org.json.JSONObject;
 | 
					import org.json.JSONObject;
 | 
				
			||||||
import org.jsoup.Jsoup;
 | 
					import org.jsoup.Jsoup;
 | 
				
			||||||
import org.jsoup.nodes.Document;
 | 
					import org.jsoup.nodes.Document;
 | 
				
			||||||
@@ -46,14 +47,225 @@ import java.util.regex.Pattern;
 | 
				
			|||||||
 * along with NewPipe.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 * along with NewPipe.  If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class YoutubeExtractor implements Extractor {
 | 
					public class YoutubeExtractor extends Extractor {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final String TAG = YoutubeExtractor.class.toString();
 | 
					    private static final String TAG = YoutubeExtractor.class.toString();
 | 
				
			||||||
 | 
					    private String pageContents;
 | 
				
			||||||
 | 
					    private Document doc;
 | 
				
			||||||
 | 
					    private JSONObject jsonObj;
 | 
				
			||||||
 | 
					    private JSONObject playerArgs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // These lists only contain itag formats that are supported by the common Android Video player.
 | 
					    // static values
 | 
				
			||||||
    // How ever if you are heading for a list showing all itag formats look at
 | 
					    private static final String DECRYPTION_FUNC_NAME="decrypt";
 | 
				
			||||||
    // https://github.com/rg3/youtube-dl/issues/1687
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // cached values
 | 
				
			||||||
 | 
					    private static volatile String decryptionCode = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public YoutubeExtractor(String pageUrl) {
 | 
				
			||||||
 | 
					        super(pageUrl);//most common videoInfo fields are now set in our superclass, for all services
 | 
				
			||||||
 | 
					        pageContents = Downloader.download(cleanUrl(pageUrl));
 | 
				
			||||||
 | 
					        doc = Jsoup.parse(pageContents, pageUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //attempt to load the youtube js player JSON arguments
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            String jsonString = matchGroup1("ytplayer.config\\s*=\\s*(\\{.*?\\});", pageContents);
 | 
				
			||||||
 | 
					            jsonObj = new JSONObject(jsonString);
 | 
				
			||||||
 | 
					            playerArgs = jsonObj.getJSONObject("args");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } catch (Exception e) {//if this fails, the video is most likely not available.
 | 
				
			||||||
 | 
					            // Determining why is done later.
 | 
				
			||||||
 | 
					            videoInfo.videoAvailableStatus = VideoInfo.VIDEO_UNAVAILABLE;
 | 
				
			||||||
 | 
					            Log.d(TAG, "Could not load JSON data for Youtube video \""+pageUrl+"\". This most likely means the video is unavailable");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //----------------------------------
 | 
				
			||||||
 | 
					        // load and parse description code, if it isn't already initialised
 | 
				
			||||||
 | 
					        //----------------------------------
 | 
				
			||||||
 | 
					        if (decryptionCode.isEmpty()) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					            // The Youtube service needs to be initialized by downloading the
 | 
				
			||||||
 | 
					            // js-Youtube-player. This is done in order to get the algorithm
 | 
				
			||||||
 | 
					            // for decrypting cryptic signatures inside certain stream urls.
 | 
				
			||||||
 | 
					                JSONObject ytAssets = jsonObj.getJSONObject("assets");
 | 
				
			||||||
 | 
					                String playerUrl = ytAssets.getString("js");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (playerUrl.startsWith("//")) {
 | 
				
			||||||
 | 
					                    playerUrl = "https:" + playerUrl;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                decryptionCode = loadDecryptionCode(playerUrl);
 | 
				
			||||||
 | 
					            } catch (Exception e){
 | 
				
			||||||
 | 
					                Log.d(TAG, "Could not load decryption code for the Youtube service.");
 | 
				
			||||||
 | 
					                e.printStackTrace();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String getTitle() {
 | 
				
			||||||
 | 
					        try {//json player args method
 | 
				
			||||||
 | 
					            return playerArgs.getString("title");
 | 
				
			||||||
 | 
					        } catch(JSONException je) {//html <meta> method
 | 
				
			||||||
 | 
					            je.printStackTrace();
 | 
				
			||||||
 | 
					            Log.w(TAG, "failed to load title from JSON args; trying to extract it from HTML");
 | 
				
			||||||
 | 
					        } try { // fall through to fall-back
 | 
				
			||||||
 | 
					            return doc.select("meta[name=title]").attr("content");
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            Log.e(TAG, "failed permanently to load title.");
 | 
				
			||||||
 | 
					            e.printStackTrace();
 | 
				
			||||||
 | 
					            return "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String getDescription() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return doc.select("p[id=\"eow-description\"]").first().html();
 | 
				
			||||||
 | 
					        } catch (Exception e) {//todo: add fallback method
 | 
				
			||||||
 | 
					            Log.e(TAG, "failed to load description.");
 | 
				
			||||||
 | 
					            e.printStackTrace();
 | 
				
			||||||
 | 
					            return "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String getUploader() {
 | 
				
			||||||
 | 
					        try {//json player args method
 | 
				
			||||||
 | 
					            return playerArgs.getString("author");
 | 
				
			||||||
 | 
					        } catch(JSONException je) {
 | 
				
			||||||
 | 
					            je.printStackTrace();
 | 
				
			||||||
 | 
					            Log.w(TAG, "failed to load uploader name from JSON args; trying to extract it from HTML");
 | 
				
			||||||
 | 
					        } try {//fall through to fallback HTML method
 | 
				
			||||||
 | 
					            return doc.select("div.yt-user-info").first().text();
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            e.printStackTrace();
 | 
				
			||||||
 | 
					            Log.e(TAG, "failed permanently to load uploader name.");
 | 
				
			||||||
 | 
					            return "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public int getLength() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return playerArgs.getInt("length_seconds");
 | 
				
			||||||
 | 
					        } catch (JSONException je) {//todo: find fallback method
 | 
				
			||||||
 | 
					            Log.e(TAG, "failed to load video duration from JSON args");
 | 
				
			||||||
 | 
					            je.printStackTrace();
 | 
				
			||||||
 | 
					            return -1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public int getViews() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            String viewCountString = doc.select("meta[itemprop=interactionCount]").attr("content");
 | 
				
			||||||
 | 
					            return Integer.parseInt(viewCountString);
 | 
				
			||||||
 | 
					        } catch (Exception e) {//todo: find fallback method
 | 
				
			||||||
 | 
					            Log.e(TAG, "failed to number of views");
 | 
				
			||||||
 | 
					            e.printStackTrace();
 | 
				
			||||||
 | 
					            return -1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String getUploadDate() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return doc.select("meta[itemprop=datePublished]").attr("content");
 | 
				
			||||||
 | 
					        } catch (Exception e) {//todo: add fallback method
 | 
				
			||||||
 | 
					            Log.e(TAG, "failed to get upload date.");
 | 
				
			||||||
 | 
					            e.printStackTrace();
 | 
				
			||||||
 | 
					            return "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String getThumbnailUrl() {
 | 
				
			||||||
 | 
					        //first attempt getting a small image version
 | 
				
			||||||
 | 
					        //in the html extracting part we try to get a thumbnail with a higher resolution
 | 
				
			||||||
 | 
					        // Try to get high resolution thumbnail if it fails use low res from the player instead
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return doc.select("link[itemprop=\"thumbnailUrl\"]").first().attr("abs:href");
 | 
				
			||||||
 | 
					        } catch(Exception e) {
 | 
				
			||||||
 | 
					            Log.w(TAG, "Could not find high res Thumbnail. Using low res instead");
 | 
				
			||||||
 | 
					            //fall through to fallback
 | 
				
			||||||
 | 
					        } try {
 | 
				
			||||||
 | 
					            return playerArgs.getString("thumbnail_url");
 | 
				
			||||||
 | 
					        } catch (JSONException je) {
 | 
				
			||||||
 | 
					            je.printStackTrace();
 | 
				
			||||||
 | 
					            Log.w(TAG, "failed to extract thumbnail URL from JSON args; trying to extract it from HTML");
 | 
				
			||||||
 | 
					            return "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public String getUploaderThumbnailUrl() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return doc.select("a[class*=\"yt-user-photo\"]").first()
 | 
				
			||||||
 | 
					                    .select("img").first()
 | 
				
			||||||
 | 
					                    .attr("abs:data-thumb");
 | 
				
			||||||
 | 
					        } catch (Exception e) {//todo: add fallback method
 | 
				
			||||||
 | 
					            Log.e(TAG, "failed to get uploader thumbnail URL.");
 | 
				
			||||||
 | 
					            e.printStackTrace();
 | 
				
			||||||
 | 
					            return "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public VideoInfo.AudioStream[] getAudioStreams() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            String dashManifest = playerArgs.getString("dashmpd");
 | 
				
			||||||
 | 
					            return parseDashManifest(dashManifest, decryptionCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } catch (NullPointerException e) {
 | 
				
			||||||
 | 
					            Log.e(TAG, "Could not find \"dashmpd\" upon the player args (maybe no dash manifest available).");
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            e.printStackTrace();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return new VideoInfo.AudioStream[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public VideoInfo.VideoStream[] getVideoStreams() {
 | 
				
			||||||
 | 
					        try{
 | 
				
			||||||
 | 
					            //------------------------------------
 | 
				
			||||||
 | 
					            // extract video stream url
 | 
				
			||||||
 | 
					            //------------------------------------
 | 
				
			||||||
 | 
					            String encoded_url_map = playerArgs.getString("url_encoded_fmt_stream_map");
 | 
				
			||||||
 | 
					            Vector<VideoInfo.VideoStream> videoStreams = new Vector<>();
 | 
				
			||||||
 | 
					            for(String url_data_str : encoded_url_map.split(",")) {
 | 
				
			||||||
 | 
					                Map<String, String> tags = new HashMap<>();
 | 
				
			||||||
 | 
					                for(String raw_tag : Parser.unescapeEntities(url_data_str, true).split("&")) {
 | 
				
			||||||
 | 
					                    String[] split_tag = raw_tag.split("=");
 | 
				
			||||||
 | 
					                    tags.put(split_tag[0], split_tag[1]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                int itag = Integer.parseInt(tags.get("itag"));
 | 
				
			||||||
 | 
					                String streamUrl = URLDecoder.decode(tags.get("url"), "UTF-8");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // if video has a signature: decrypt it and add it to the url
 | 
				
			||||||
 | 
					                if(tags.get("s") != null) {
 | 
				
			||||||
 | 
					                    streamUrl = streamUrl + "&signature=" + decryptSignature(tags.get("s"), decryptionCode);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if(resolveFormat(itag) != -1) {
 | 
				
			||||||
 | 
					                    videoStreams.add(new VideoInfo.VideoStream(
 | 
				
			||||||
 | 
					                            streamUrl,
 | 
				
			||||||
 | 
					                            resolveFormat(itag),
 | 
				
			||||||
 | 
					                            resolveResolutionString(itag)));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return videoStreams.toArray(new VideoInfo.VideoStream[videoStreams.size()]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            Log.e(TAG, "Failed to get video stream");
 | 
				
			||||||
 | 
					            e.printStackTrace();
 | 
				
			||||||
 | 
					            return new VideoInfo.VideoStream[0];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**These lists only contain itag formats that are supported by the common Android Video player.
 | 
				
			||||||
 | 
					    However if you are looking for a list showing all itag formats, look at
 | 
				
			||||||
 | 
					    https://github.com/rg3/youtube-dl/issues/1687 */
 | 
				
			||||||
    public static int resolveFormat(int itag) {
 | 
					    public static int resolveFormat(int itag) {
 | 
				
			||||||
        switch(itag) {
 | 
					        switch(itag) {
 | 
				
			||||||
            // video
 | 
					            // video
 | 
				
			||||||
@@ -91,68 +303,28 @@ public class YoutubeExtractor implements Extractor {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // static values
 | 
					 | 
				
			||||||
    private static final String DECRYPTION_FUNC_NAME="decrypt";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // cached values
 | 
					 | 
				
			||||||
    private static volatile String decryptionCode = "";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void initService(String site) {
 | 
					 | 
				
			||||||
        // The Youtube service needs to be initialized by downloading the
 | 
					 | 
				
			||||||
        // js-Youtube-player. This is done in order to get the algorithm
 | 
					 | 
				
			||||||
        // for decrypting cryptic signatures inside certain stream urls.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Star Wars Kid is used as a dummy video, in order to download the youtube player.
 | 
					 | 
				
			||||||
        //String site = Downloader.download("https://www.youtube.com/watch?v=HPPj6viIBmU");
 | 
					 | 
				
			||||||
        //-------------------------------------
 | 
					 | 
				
			||||||
        // extracting form player args
 | 
					 | 
				
			||||||
        //-------------------------------------
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            String jsonString = matchGroup1("ytplayer.config\\s*=\\s*(\\{.*?\\});", site);
 | 
					 | 
				
			||||||
            JSONObject jsonObj = new JSONObject(jsonString);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //----------------------------------
 | 
					 | 
				
			||||||
            // load and parse description code
 | 
					 | 
				
			||||||
            //----------------------------------
 | 
					 | 
				
			||||||
            if (decryptionCode.isEmpty()) {
 | 
					 | 
				
			||||||
                JSONObject ytAssets = jsonObj.getJSONObject("assets");
 | 
					 | 
				
			||||||
                String playerUrl = ytAssets.getString("js");
 | 
					 | 
				
			||||||
                if (playerUrl.startsWith("//")) {
 | 
					 | 
				
			||||||
                    playerUrl = "https:" + playerUrl;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                decryptionCode = loadDecryptionCode(playerUrl);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        } catch (Exception e){
 | 
					 | 
				
			||||||
            Log.d(TAG, "Could not initialize the extractor of the Youtube service.");
 | 
					 | 
				
			||||||
            e.printStackTrace();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public String getVideoId(String videoUrl) {
 | 
					    public String getVideoId(String url) {
 | 
				
			||||||
        String id = "";
 | 
					        String id;
 | 
				
			||||||
        Pattern pat;
 | 
					        String pat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(videoUrl.contains("youtube")) {
 | 
					        if(url.contains("youtube")) {
 | 
				
			||||||
            pat = Pattern.compile("youtube\\.com/watch\\?v=([\\-a-zA-Z0-9_]{11})");
 | 
					            pat = "youtube\\.com/watch\\?v=([\\-a-zA-Z0-9_]{11})";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if(videoUrl.contains("youtu.be")) {
 | 
					        else if(url.contains("youtu.be")) {
 | 
				
			||||||
            pat = Pattern.compile("youtu\\.be/([a-zA-Z0-9_-]{11})");
 | 
					            pat = "youtu\\.be/([a-zA-Z0-9_-]{11})";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            Log.e(TAG, "Error could not parse url: " + videoUrl);
 | 
					            Log.e(TAG, "Error could not parse url: " + url);
 | 
				
			||||||
            return "";
 | 
					            return "";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Matcher mat = pat.matcher(videoUrl);
 | 
					        id = matchGroup1(pat, url);
 | 
				
			||||||
        boolean foundMatch = mat.find();
 | 
					        if(!id.isEmpty()){
 | 
				
			||||||
        if(foundMatch){
 | 
					            Log.i(TAG, "string \""+url+"\" matches!");
 | 
				
			||||||
            id = mat.group(1);
 | 
					            return id;
 | 
				
			||||||
            Log.i(TAG, "string \""+videoUrl+"\" matches!");
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Log.i(TAG, "string \""+videoUrl+"\" does not match.");
 | 
					        Log.i(TAG, "string \""+url+"\" does not match.");
 | 
				
			||||||
        return id;
 | 
					        return "";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@@ -160,118 +332,47 @@ public class YoutubeExtractor implements Extractor {
 | 
				
			|||||||
        return "https://www.youtube.com/watch?v=" + videoId;
 | 
					        return "https://www.youtube.com/watch?v=" + videoId;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public int getStartPosition(String siteUrl){
 | 
					    /**Attempts to parse (and return) the offset to start playing the video from.
 | 
				
			||||||
        String timeStamp = matchGroup1("((#|&)t=\\d{0,3}h?\\d{0,3}m?\\d{1,3}s?)", siteUrl);
 | 
					     * @return the offset (in seconds), or 0 if no timestamp is found.*/
 | 
				
			||||||
        Log.i(TAG, "time stamp:"+timeStamp);
 | 
					    @Override
 | 
				
			||||||
        //videoInfo.startPosition
 | 
					    public int getTimeStamp(){
 | 
				
			||||||
 | 
					        String timeStamp = matchGroup1("((#|&)t=\\d{0,3}h?\\d{0,3}m?\\d{1,3}s?)", pageUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //TODO: test this!
 | 
					        //TODO: test this
 | 
				
			||||||
        if(timeStamp.length() > 0) {
 | 
					        if(!timeStamp.isEmpty()) {
 | 
				
			||||||
            String secondsString = matchGroup1("(\\d{1,3})s", timeStamp);
 | 
					            String secondsString = matchGroup1("(\\d{1,3})s", timeStamp);
 | 
				
			||||||
            if(secondsString.length() == 0)//try again with unspecified units as seconds
 | 
					 | 
				
			||||||
                secondsString = matchGroup1("t=(\\d{1,3})", timeStamp);
 | 
					 | 
				
			||||||
            String minutesString = matchGroup1("(\\d{1,3})m", timeStamp);
 | 
					            String minutesString = matchGroup1("(\\d{1,3})m", timeStamp);
 | 
				
			||||||
            String hoursString = matchGroup1("(\\d{1,3})h", timeStamp);
 | 
					            String hoursString = matchGroup1("(\\d{1,3})h", timeStamp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            int seconds = (secondsString.length() > 0 ? Integer.parseInt(secondsString) : 0);
 | 
					            if(secondsString.isEmpty()//if nothing was got,
 | 
				
			||||||
            int minutes = (minutesString.length() > 0 ? Integer.parseInt(minutesString) : 0);
 | 
					            && minutesString.isEmpty()//treat as unlabelled seconds
 | 
				
			||||||
            int hours =   (hoursString.length()   > 0 ? Integer.parseInt(hoursString)   : 0);
 | 
					            && hoursString.isEmpty())
 | 
				
			||||||
 | 
					                secondsString = matchGroup1("t=(\\d{1,3})", timeStamp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return seconds + (60*minutes) + (3600*hours);//don't trust BODMAS!
 | 
					            int seconds = (secondsString.isEmpty() ? 0 : Integer.parseInt(secondsString));
 | 
				
			||||||
 | 
					            int minutes = (minutesString.isEmpty() ? 0 : Integer.parseInt(minutesString));
 | 
				
			||||||
 | 
					            int hours =   (hoursString.isEmpty()   ? 0 : Integer.parseInt(hoursString));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            int ret = seconds + (60*minutes) + (3600*hours);//don't trust BODMAS!
 | 
				
			||||||
 | 
					            Log.d(TAG, "derived timestamp value:"+ret);
 | 
				
			||||||
 | 
					            return ret;
 | 
				
			||||||
            //the ordering varies internationally
 | 
					            //the ordering varies internationally
 | 
				
			||||||
        }//else, return default 0
 | 
					        }//else, return default 0
 | 
				
			||||||
        return 0;
 | 
					        return 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public VideoInfo getVideoInfo(String siteUrl) {
 | 
					    public VideoInfo getVideoInfo() {
 | 
				
			||||||
        String site = Downloader.download(siteUrl);
 | 
					        videoInfo = super.getVideoInfo();
 | 
				
			||||||
        VideoInfo videoInfo = new VideoInfo();
 | 
					        //todo: replace this with a call to getVideoId, if possible
 | 
				
			||||||
 | 
					        videoInfo.id = matchGroup1("v=([0-9a-zA-Z_-]{11})", pageUrl);
 | 
				
			||||||
        Document doc = Jsoup.parse(site, siteUrl);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        videoInfo.id = matchGroup1("v=([0-9a-zA-Z_-]{11})", siteUrl);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        videoInfo.age_limit = 0;
 | 
					        videoInfo.age_limit = 0;
 | 
				
			||||||
        videoInfo.webpage_url = siteUrl;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        initService(site);
 | 
					        //average rating
 | 
				
			||||||
 | 
					 | 
				
			||||||
        //-------------------------------------
 | 
					 | 
				
			||||||
        // extracting form player args
 | 
					 | 
				
			||||||
        //-------------------------------------
 | 
					 | 
				
			||||||
        JSONObject playerArgs = null;
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
                String jsonString = matchGroup1("ytplayer.config\\s*=\\s*(\\{.*?\\});", site);
 | 
					 | 
				
			||||||
                JSONObject jsonObj = new JSONObject(jsonString);
 | 
					 | 
				
			||||||
                playerArgs = jsonObj.getJSONObject("args");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (Exception e) {
 | 
					 | 
				
			||||||
                e.printStackTrace();
 | 
					 | 
				
			||||||
                // If we fail in this part the video is most likely not available.
 | 
					 | 
				
			||||||
                // Determining why is done later.
 | 
					 | 
				
			||||||
                videoInfo.videoAvailableStatus = VideoInfo.VIDEO_UNAVAILABLE;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //-----------------------
 | 
					 | 
				
			||||||
        // load and extract audio
 | 
					 | 
				
			||||||
        //-----------------------
 | 
					 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            String dashManifest = playerArgs.getString("dashmpd");
 | 
					 | 
				
			||||||
            videoInfo.audioStreams = parseDashManifest(dashManifest, decryptionCode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        } catch (NullPointerException e) {
 | 
					 | 
				
			||||||
            Log.e(TAG, "Could not find \"dashmpd\" upon the player args (maybe no dash manifest available).");
 | 
					 | 
				
			||||||
        } catch (Exception e) {
 | 
					 | 
				
			||||||
            e.printStackTrace();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            //--------------------------------------------
 | 
					 | 
				
			||||||
            // extract general information about the video
 | 
					 | 
				
			||||||
            //--------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            videoInfo.uploader = playerArgs.getString("author");
 | 
					 | 
				
			||||||
            videoInfo.title = playerArgs.getString("title");
 | 
					 | 
				
			||||||
            //first attempt getting a small image version
 | 
					 | 
				
			||||||
            //in the html extracting part we try to get a thumbnail with a higher resolution
 | 
					 | 
				
			||||||
            videoInfo.thumbnail_url = playerArgs.getString("thumbnail_url");
 | 
					 | 
				
			||||||
            videoInfo.duration = playerArgs.getInt("length_seconds");
 | 
					 | 
				
			||||||
            videoInfo.average_rating = playerArgs.getString("avg_rating");
 | 
					            videoInfo.average_rating = playerArgs.getString("avg_rating");
 | 
				
			||||||
 | 
					        } catch (JSONException e) {
 | 
				
			||||||
            //------------------------------------
 | 
					 | 
				
			||||||
            // extract video stream url
 | 
					 | 
				
			||||||
            //------------------------------------
 | 
					 | 
				
			||||||
            String encoded_url_map = playerArgs.getString("url_encoded_fmt_stream_map");
 | 
					 | 
				
			||||||
            Vector<VideoInfo.VideoStream> videoStreams = new Vector<>();
 | 
					 | 
				
			||||||
            for(String url_data_str : encoded_url_map.split(",")) {
 | 
					 | 
				
			||||||
                Map<String, String> tags = new HashMap<>();
 | 
					 | 
				
			||||||
                for(String raw_tag : Parser.unescapeEntities(url_data_str, true).split("&")) {
 | 
					 | 
				
			||||||
                    String[] split_tag = raw_tag.split("=");
 | 
					 | 
				
			||||||
                    tags.put(split_tag[0], split_tag[1]);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                int itag = Integer.parseInt(tags.get("itag"));
 | 
					 | 
				
			||||||
                String streamUrl = URLDecoder.decode(tags.get("url"), "UTF-8");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // if video has a signature: decrypt it and add it to the url
 | 
					 | 
				
			||||||
                if(tags.get("s") != null) {
 | 
					 | 
				
			||||||
                    streamUrl = streamUrl + "&signature=" + decryptSignature(tags.get("s"), decryptionCode);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if(resolveFormat(itag) != -1) {
 | 
					 | 
				
			||||||
                    videoStreams.add(new VideoInfo.VideoStream(
 | 
					 | 
				
			||||||
                            streamUrl,
 | 
					 | 
				
			||||||
                            resolveFormat(itag),
 | 
					 | 
				
			||||||
                            resolveResolutionString(itag)));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            videoInfo.videoStreams =
 | 
					 | 
				
			||||||
                    videoStreams.toArray(new VideoInfo.VideoStream[videoStreams.size()]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        } catch (Exception e) {
 | 
					 | 
				
			||||||
            e.printStackTrace();
 | 
					            e.printStackTrace();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -279,7 +380,6 @@ public class YoutubeExtractor implements Extractor {
 | 
				
			|||||||
        // extracting information from html page
 | 
					        // extracting information from html page
 | 
				
			||||||
        //---------------------------------------
 | 
					        //---------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Determine what went wrong when the Video is not available
 | 
					        // Determine what went wrong when the Video is not available
 | 
				
			||||||
        if(videoInfo.videoAvailableStatus == VideoInfo.VIDEO_UNAVAILABLE) {
 | 
					        if(videoInfo.videoAvailableStatus == VideoInfo.VIDEO_UNAVAILABLE) {
 | 
				
			||||||
            if(doc.select("h1[id=\"unavailable-message\"]").first().text().contains("GEMA")) {
 | 
					            if(doc.select("h1[id=\"unavailable-message\"]").first().text().contains("GEMA")) {
 | 
				
			||||||
@@ -287,22 +387,6 @@ public class YoutubeExtractor implements Extractor {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Try to get high resolution thumbnail if it fails use low res from the player instead
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            videoInfo.thumbnail_url = doc.select("link[itemprop=\"thumbnailUrl\"]").first()
 | 
					 | 
				
			||||||
                    .attr("abs:href");
 | 
					 | 
				
			||||||
        } catch(Exception e) {
 | 
					 | 
				
			||||||
            Log.i(TAG, "Could not find high res Thumbnail. Using low res instead");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // upload date
 | 
					 | 
				
			||||||
        videoInfo.upload_date = doc.select("meta[itemprop=datePublished]").attr("content");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //TODO: Format date locale-specifically
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // description
 | 
					 | 
				
			||||||
        videoInfo.description = doc.select("p[id=\"eow-description\"]").first().html();
 | 
					 | 
				
			||||||
        String likesString = "";
 | 
					        String likesString = "";
 | 
				
			||||||
        String dislikesString = "";
 | 
					        String dislikesString = "";
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
@@ -325,17 +409,8 @@ public class YoutubeExtractor implements Extractor {
 | 
				
			|||||||
            videoInfo.dislike_count = 0;
 | 
					            videoInfo.dislike_count = 0;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // uploader thumbnail
 | 
					 | 
				
			||||||
        videoInfo.uploader_thumbnail_url = doc.select("a[class*=\"yt-user-photo\"]").first()
 | 
					 | 
				
			||||||
                .select("img").first()
 | 
					 | 
				
			||||||
                .attr("abs:data-thumb");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // view count TODO:  locale-specific formatting
 | 
					 | 
				
			||||||
        String viewCountString = doc.select("meta[itemprop=interactionCount]").attr("content");
 | 
					 | 
				
			||||||
        videoInfo.view_count = Integer.parseInt(viewCountString);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // next video
 | 
					        // next video
 | 
				
			||||||
        videoInfo.nextVideo = extractVideoInfoItem(doc.select("div[class=\"watch-sidebar-section\"]").first()
 | 
					        videoInfo.nextVideo = extractVideoPreviewInfo(doc.select("div[class=\"watch-sidebar-section\"]").first()
 | 
				
			||||||
                .select("li").first());
 | 
					                .select("li").first());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // related videos
 | 
					        // related videos
 | 
				
			||||||
@@ -343,13 +418,14 @@ public class YoutubeExtractor implements Extractor {
 | 
				
			|||||||
        for(Element li : doc.select("ul[id=\"watch-related\"]").first().children()) {
 | 
					        for(Element li : doc.select("ul[id=\"watch-related\"]").first().children()) {
 | 
				
			||||||
            // first check if we have a playlist. If so leave them out
 | 
					            // first check if we have a playlist. If so leave them out
 | 
				
			||||||
            if(li.select("a[class*=\"content-link\"]").first() != null) {
 | 
					            if(li.select("a[class*=\"content-link\"]").first() != null) {
 | 
				
			||||||
                relatedVideos.add(extractVideoInfoItem(li));
 | 
					                relatedVideos.add(extractVideoPreviewInfo(li));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        videoInfo.relatedVideos = relatedVideos.toArray(new VideoPreviewInfo[relatedVideos.size()]);
 | 
					        videoInfo.relatedVideos = relatedVideos.toArray(new VideoPreviewInfo[relatedVideos.size()]);
 | 
				
			||||||
        return videoInfo;
 | 
					        return videoInfo;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private VideoInfo.AudioStream[] parseDashManifest(String dashManifest, String decryptoinCode) {
 | 
					    private VideoInfo.AudioStream[] parseDashManifest(String dashManifest, String decryptoinCode) {
 | 
				
			||||||
        if(!dashManifest.contains("/signature/")) {
 | 
					        if(!dashManifest.contains("/signature/")) {
 | 
				
			||||||
            String encryptedSig = matchGroup1("/s/([a-fA-F0-9\\.]+)", dashManifest);
 | 
					            String encryptedSig = matchGroup1("/s/([a-fA-F0-9\\.]+)", dashManifest);
 | 
				
			||||||
@@ -413,8 +489,10 @@ public class YoutubeExtractor implements Extractor {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return audioStreams.toArray(new VideoInfo.AudioStream[audioStreams.size()]);
 | 
					        return audioStreams.toArray(new VideoInfo.AudioStream[audioStreams.size()]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    /**Provides information about links to other videos on the video page, such as related videos.
 | 
				
			||||||
    private VideoPreviewInfo extractVideoInfoItem(Element li) {
 | 
					     * This is encapsulated in a VideoPreviewInfo object,
 | 
				
			||||||
 | 
					     * which is a subset of the fields in a full VideoInfo.*/
 | 
				
			||||||
 | 
					    private VideoPreviewInfo extractVideoPreviewInfo(Element li) {
 | 
				
			||||||
        VideoPreviewInfo info = new VideoPreviewInfo();
 | 
					        VideoPreviewInfo info = new VideoPreviewInfo();
 | 
				
			||||||
        info.webpage_url = li.select("a[class*=\"content-link\"]").first()
 | 
					        info.webpage_url = li.select("a[class*=\"content-link\"]").first()
 | 
				
			||||||
                .attr("abs:href");
 | 
					                .attr("abs:href");
 | 
				
			||||||
@@ -426,8 +504,10 @@ public class YoutubeExtractor implements Extractor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        //todo: check NullPointerException causing
 | 
					        //todo: check NullPointerException causing
 | 
				
			||||||
        info.title = li.select("span[class=\"title\"]").first().text();
 | 
					        info.title = li.select("span[class=\"title\"]").first().text();
 | 
				
			||||||
        info.view_count = Long.parseLong(li.select("span[class*=\"view-count\"]").first().text());
 | 
					        info.view_count = Long.parseLong(li.select("span[class*=\"view-count\"]")
 | 
				
			||||||
 | 
					                .first().text().replaceAll("[^\\d]", ""));
 | 
				
			||||||
        info.uploader = li.select("span[class=\"g-hovercard\"]").first().text();
 | 
					        info.uploader = li.select("span[class=\"g-hovercard\"]").first().text();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        info.duration = li.select("span[class=\"video-time\"]").first().text();
 | 
					        info.duration = li.select("span[class=\"video-time\"]").first().text();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Element img = li.select("img").first();
 | 
					        Element img = li.select("img").first();
 | 
				
			||||||
@@ -491,15 +571,19 @@ public class YoutubeExtractor implements Extractor {
 | 
				
			|||||||
        return result.toString();
 | 
					        return result.toString();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String cleanUrl(String complexUrl) {
 | 
				
			||||||
 | 
					        return getVideoUrl(getVideoId(complexUrl));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String matchGroup1(String pattern, String input) {
 | 
					    private String matchGroup1(String pattern, String input) {
 | 
				
			||||||
        Pattern pat = Pattern.compile(pattern);
 | 
					        Pattern pat = Pattern.compile(pattern);
 | 
				
			||||||
        Matcher mat = pat.matcher(input);
 | 
					        Matcher mat = pat.matcher(input);
 | 
				
			||||||
        boolean foundMatch = mat.find();
 | 
					        boolean foundMatch = mat.find();
 | 
				
			||||||
        if(foundMatch){
 | 
					        if (foundMatch) {
 | 
				
			||||||
            return mat.group(1);
 | 
					            return mat.group(1);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            Log.e(TAG, "failed to find pattern \""+pattern+"\" inside of \""+input+"\"");
 | 
					            Log.w(TAG, "failed to find pattern \""+pattern+"\" inside of \""+input+"\"");
 | 
				
			||||||
            new Exception("failed to find pattern \""+pattern+"\"").printStackTrace();
 | 
					            new Exception("failed to find pattern \""+pattern+"\"").printStackTrace();
 | 
				
			||||||
            return "";
 | 
					            return "";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,7 +62,7 @@ public class YoutubeSearchEngine implements SearchEngine {
 | 
				
			|||||||
        String site;
 | 
					        String site;
 | 
				
			||||||
        String url = builder.build().toString();
 | 
					        String url = builder.build().toString();
 | 
				
			||||||
        //if we've been passed a valid language code, append it to the URL
 | 
					        //if we've been passed a valid language code, append it to the URL
 | 
				
			||||||
        if(languageCode.length() > 0) {
 | 
					        if(!languageCode.isEmpty()) {
 | 
				
			||||||
          //assert Pattern.matches("[a-z]{2}(-([A-Z]{2}|[0-9]{1,3}))?", languageCode);
 | 
					          //assert Pattern.matches("[a-z]{2}(-([A-Z]{2}|[0-9]{1,3}))?", languageCode);
 | 
				
			||||||
                site  = Downloader.download(url, languageCode);
 | 
					                site  = Downloader.download(url, languageCode);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -101,6 +101,7 @@ public class YoutubeSearchEngine implements SearchEngine {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                // video item type
 | 
					                // video item type
 | 
				
			||||||
            } else if(!((el = item.select("div[class*=\"yt-lockup-video\"").first()) == null)) {
 | 
					            } else if(!((el = item.select("div[class*=\"yt-lockup-video\"").first()) == null)) {
 | 
				
			||||||
 | 
					                //todo: de-duplicate this with YoutubeExtractor.getVideoPreviewInfo()
 | 
				
			||||||
                VideoPreviewInfo resultItem = new VideoPreviewInfo();
 | 
					                VideoPreviewInfo resultItem = new VideoPreviewInfo();
 | 
				
			||||||
                Element dl = el.select("h3").first().select("a").first();
 | 
					                Element dl = el.select("h3").first().select("a").first();
 | 
				
			||||||
                resultItem.webpage_url = dl.attr("abs:href");
 | 
					                resultItem.webpage_url = dl.attr("abs:href");
 | 
				
			||||||
@@ -113,8 +114,9 @@ public class YoutubeSearchEngine implements SearchEngine {
 | 
				
			|||||||
                    e.printStackTrace();
 | 
					                    e.printStackTrace();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                resultItem.title = dl.text();
 | 
					                resultItem.title = dl.text();
 | 
				
			||||||
                resultItem.duration = item.select("span[class=\"video-time\"]").first()
 | 
					
 | 
				
			||||||
                        .text();
 | 
					                resultItem.duration = item.select("span[class=\"video-time\"]").first().text();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                resultItem.uploader = item.select("div[class=\"yt-lockup-byline\"]").first()
 | 
					                resultItem.uploader = item.select("div[class=\"yt-lockup-byline\"]").first()
 | 
				
			||||||
                        .select("a").first()
 | 
					                        .select("a").first()
 | 
				
			||||||
                        .text();
 | 
					                        .text();
 | 
				
			||||||
@@ -132,7 +134,7 @@ public class YoutubeSearchEngine implements SearchEngine {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                result.resultList.add(resultItem);
 | 
					                result.resultList.add(resultItem);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                Log.e(TAG, "GREAT FUCKING ERROR");
 | 
					                Log.e(TAG, "unexpected element found:\""+el+"\"");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return result;
 | 
					        return result;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,8 +33,13 @@ public class YoutubeService implements StreamingService {
 | 
				
			|||||||
        return serviceInfo;
 | 
					        return serviceInfo;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Extractor getExtractorInstance() {
 | 
					    public Extractor getExtractorInstance(String url) {
 | 
				
			||||||
        return new YoutubeExtractor();
 | 
					        if(acceptUrl(url)) {
 | 
				
			||||||
 | 
					            return new YoutubeExtractor(url);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            throw new IllegalArgumentException("supplied String is not a valid Youtube URL");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public SearchEngine getSearchEngineInstance() {
 | 
					    public SearchEngine getSearchEngineInstance() {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user