Zillow Tech Hub

Video Walkthroughs for the Zillow Android App

Zillow empowers home shoppers to make informed decisions by providing them with as much information as possible about each listing. Also, agents and home sellers need to be able to highlight each home’s key features and layout. Moving to providing home videos, in addition to the standard photo gallery, is a natural progression.

In February 2016, Zillow launched video walkthroughs, a free feature that allows agents and home sellers to use their phones to capture and upload a two-minute video that highlights the layout and features of the home.  Videos can be recorded in short segments, versus one continuous video, so that agents can show different views and shots of the home easily. Once published, listings with videos are prominently displayed in search results and on each home’s detail page, giving home shoppers a new and more immersive way to envision themselves in a home.

In this post we will review the technical challenges of building video capture for the Android platform.

Video Capture

Because devices in the Android world are so diverse, we first decided to restrict video capture to certain devices. This allowed us to focus on quality as well as to minimize technical complexity. The minimum feature set and requirements we settled on were:

Video Recording

Although Android’s deprecated Camera API and MediaRecorder are not feature rich, we still ended up using these classes.  The reason is that we needed to support Android API 14+ at the time and our use cases did not dictate the need for something more complex. In the end we shipped with a SurfaceView for pre-JellyBean(JB) devices and a TextureView for JellyBean and above (because MediaRecorder pre-JB cannot be used with an output SurfaceTexture).

Preparing the CamcorderProfile

Configuring the CamcorderProfile is pretty simple. The profile is based on the 720p predefined format in the Android OS and the output video format is an mp4 with a specific bit rate.

public CamcorderProfile getCamcorderProfile()
{
    // If 720p recording cannot be done then camcorder profile cannot be created!
    if( !CamcorderProfile.hasProfile( CamcorderProfile.QUALITY_720P ) )
    {
        return null;
    }

    mCamcorderProfile = CamcorderProfile.get( CamcorderProfile.QUALITY_720P );
    mCamcorderProfile.fileFormat = MediaRecorder.OutputFormat.MPEG_4;
    mCamcorderProfile.videoCodec = MediaRecorder.VideoEncoder.H264;
    mCamcorderProfile.videoBitRate = VIDEO_ENCODING_BIT_RATE;
    return mCamcorderProfile;
}

Preparing the Camera

The camera preparation is straightforward and there is plenty of Android documentation. Some of the key things to keep in mind are:

mPreview.setSurfaceTextureListener( this );

 

@Override
public void onSurfaceTextureAvailable( SurfaceTexture surface, int width, int height )
{
    prepareCamera();
}

@Override
public boolean onSurfaceTextureDestroyed( SurfaceTexture surface )
{
    releaseCamera();
    return true;
}

 

Camera.Parameters parameters = mCamera.getParameters();

// Apply focus mode to continuous video if available
List<String> focusModes = parameters.getSupportedFocusModes();
if ( focusModes.contains( Parameters.FOCUS_MODE_CONTINUOUS_VIDEO ) )
{
    parameters.setFocusMode( Parameters.FOCUS_MODE_CONTINUOUS_VIDEO );
}

// Setup the preview size
parameters.setPreviewSize( profile.videoFrameWidth, profile.videoFrameHeight );
mCamera.setParameters( parameters );

 

if( AndroidCompatibility.isAndroidJellyBeanOrNewer() )
{
    mCamera.setPreviewTexture( mPreview.getSurfaceTexture() );
}
else
{
    mCamera.setPreviewDisplay( mSurfaceHolder );
}
mCamera.startPreview();

 

mCamera.setDisplayOrientation( NATURAL_CAMERA_ROTATION_DEGREE );

 

mCamera.stopPreview();

// release the camera for other applications
mCamera.release();
mCamera = null;

 

Preparing Media Recorder

We did not face any problems preparing the media recorder. The sequence of events is simply:

The only thing to keep in mind is that the video recorder is a time consuming operation so it’s best to get it done in another thread. An AsyncTask works well for this.

@Override
protected Boolean doInBackground( Void... voids )
{
    // initialize video camera
    if ( prepareVideoRecorder() )
    {
        // Camera is available and unlocked, MediaRecorder is prepared,
        // so start recording
        mMediaRecorder.start();
    }
    else
    {
        return false;
    }
    return true;
}

 

Join Multiple Video Segments

Users need to be able to both pause recording and restart video capture. Android does not provide an API or a mechanism to handle this. We researched multiple solutions but settled on MP4Parser.

It’s a well written and stable Java library that is fast enough. As soon as a user accepts a video segment, we start to append it to an already existing video.

// Create a movie array
Movie[] inMovies = new Movie[] {
MovieCreator.build( filePath1 ),
MovieCreator.build( filePath2 ) };
List<Track> videoTracks = new LinkedList<Track>();

for ( Movie m : inMovies )
{
    for ( Track t : m.getTracks() )
    {
        if ( t.getHandler().equals( VIDEO_TAG ) )
        {
            videoTracks.add(t);
        }
    }
}

Movie result = new Movie();
if ( videoTracks.size() > 0 )
{
    result.addTrack( new AppendTrack( videoTracks.toArray( new Track[videoTracks.size()] ) ) );
}
Container out = new DefaultMp4Builder().build( result );

// Write out the file to disk
FileChannel fc = new RandomAccessFile( outputFile, "rw" ).getChannel();
out.writeContainer( fc );
fc.close();

Extract Cover Frame

Videos need to have a cover photo. Android has provided MediaMetadataRetriever to extract and retrieve frames from a video. Once all the videos are stitched together we extract out a frame from the video. Since the extraction is based on a microsecond, we extract the first microsecond of the video. All of this work should be done in an AsyncTask since it is a time consuming operation.

// Open up the media metadata retriever
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource( mVideoPath );
Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime( mTimeToExtractInMicros, MediaMetadataRetriever.OPTION_CLOSEST_SYNC );

// Need to release when done
mediaMetadataRetriever.release();

Replaying Video

We use Exoplayer to play the video. Although the Android provided MediaPlayer would have been sufficient to play our video, we prefer to use Exoplayer because of Google’s usage and support. Why Exoplayer is not provided in the Android OS could be a discussion for another day…

Our Results

Since launch, we’ve found that adding a video walkthrough improves home shopper engagement with each listing.  Listings with video walkthroughs get more than twice the amount of pageviews and twice the amount of saves compared to listings without a video walkthrough, and adding a video walkthrough is a great way to increase the number of contacts a listing can generate.

Exit mobile version