Recherche avancée

Médias (0)

Mot : - Tags -/signalement

Aucun média correspondant à vos critères n’est disponible sur le site.

Autres articles (41)

  • Les vidéos

    21 avril 2011, par

    Comme les documents de type "audio", Mediaspip affiche dans la mesure du possible les vidéos grâce à la balise html5 .
    Un des inconvénients de cette balise est qu’elle n’est pas reconnue correctement par certains navigateurs (Internet Explorer pour ne pas le nommer) et que chaque navigateur ne gère en natif que certains formats de vidéos.
    Son avantage principal quant à lui est de bénéficier de la prise en charge native de vidéos dans les navigateur et donc de se passer de l’utilisation de Flash et (...)

  • Use, discuss, criticize

    13 avril 2011, par

    Talk to people directly involved in MediaSPIP’s development, or to people around you who could use MediaSPIP to share, enhance or develop their creative projects.
    The bigger the community, the more MediaSPIP’s potential will be explored and the faster the software will evolve.
    A discussion list is available for all exchanges between users.

  • Publier sur MédiaSpip

    13 juin 2013

    Puis-je poster des contenus à partir d’une tablette Ipad ?
    Oui, si votre Médiaspip installé est à la version 0.2 ou supérieure. Contacter au besoin l’administrateur de votre MédiaSpip pour le savoir

Sur d’autres sites (6034)

  • How you can use the Piwik AOM plugin to improve your data and make better online marketing decisions

    Hi, this is André, one of the authors of the Piwik Advanced Online Marketing plugin, which has just hit 5,000 downloads on the Piwik marketplace. In this blog post I’ll show you how Piwik AOM improves your data and enables you to make better online marketing decisions.

    Piwik itself is excellent in tracking all kinds of visitor data, like where a visitor is coming from and what he’s doing on your page or app (pageviews, events, conversions). But what Piwik did not yet take a closer a look at, is how much you’ve invested into your marketing activities and how profitable they are.

    With the Piwik AOM plugin you can integrate data like advertising costs, advertising campaign names, ad impressions etc. from advertising platforms (such as Google AdWords, Microsoft Bing, Criteo, Facebook Ads and Taboola) and individual campaigns (such as such as cost per view/click/acquisition and fixed price per months deals) into Piwik and combine that data with individual Piwik visits.

    Piwik AOM adds a new marketing performance report to Piwik giving you a great overview of all your marketing activities with drill-down functionality :

    Piwik AOM Marketing Performance Report

     

    When taking a look at a specific visitor, Piwik AOM shows you the exact cost of acquiring a specific visit :

    Piwik AOM Visitor Profile Popup

     

    Leveraging Piwik AOM’s full potential

    But although you can access Piwik AOM’s valuable data directly in the Piwik UI for ad-hoc analyses, Piwik AOM’s true strength comes into play when working with the raw data in an external business intelligence application of your choice, where you can further integrate Piwik AOM’s data with your most accurate backend data (like conversion’s contribution margins after returns, new vs. existing customer, etc.).

    Piwik AOM offers some API endpoints that allow you to fetch the data you need but you can also retrieve it directly from Piwik AOM’s aom_visits table, which includes all visits, all allocated advertising costs and advertising campaign details. As there is never data being deleted from aom_visits, the table can easily be connected to your ETL tool with its last update timestamp column. A third way to get data out of Piwik AOM is by developing your own Piwik plugin and listening to the AOM.aomVisitAddedOrUpdated event, which is posted whenever an aom_visits record is added or updated.

    Integrating Piwik AOM’s data with your backend data in the business intelligence application of your choice allows you to evaluate the real performance of your online marketing campaigns when applying different conversion attribution models, conduct customer journey analyses, create sophisticated forecasts and whatever you can think of.

    AOM Use case

    A company that followed this approach, is FINANZCHECK.de, one of Germany’s leading loan comparison websites. At the eMetrics summit 2016 in Berlin, Germany, I gave a talk about FINANZCHECK’s architectural online marketing setup. Until recently, FINANZCHECK used Pentaho data integration to integrate data from Piwik, Piwik AOM and additional internal tools like its proprietary CRM software into Jaspersoft, its data warehouse an BI solution. The enriched data in Jaspersoft was not only used for reporting to various stakeholders but also for optimising all kinds of marketing activities (e.g. bids for individual keywords in Google AdWords) and proactive alerting. Not long ago, FINANZCHECK started an initiative to improve its setup even further – I’ll hopefully be able to cover this in a more detailed case study soon.

    Roadmap

    In the past, we had the chance to make great progress in developing this plugin by solving specific requirements of different companies who use Piwik AOM. During the next months, we plan to integrate more advertising platforms, reimplement Facebook Ads, improve the support of individual campaigns and work on the general plugin stability and performance.

    Before you install Piwik AOM

    Before installing Piwik AOM, you should know that its initial setup and even its maintenance can be quite complex. Piwik AOM will heavily modify your Piwik installation and you will only benefit from Piwik AOM if you are willing to invest quite some time into it.

    If you are not familiar with Piwik’s internals, PHP, MySQL, database backups, cronjobs, creating API accounts at the advertising platforms or adding parameters to your advertising campaign’s URLs, you should probably not install it on your own (at least not in your production environment).

    Piwik AOM has successfully been tested with up to 25k visitors a day for a period of more than two years, running on an AWS server with 4 GB RAM, once CPU and a separate AWS RDS MySQL database.

    Ideas and Support

    If you have ideas for new features or need support with your Piwik AOM installation or leveraging your marketing data’s potential in general, feel free to get in touch with the plugin’s co-author Daniel or me. You can find our contact details on the plugin’s website http://www.advanced-online-marketing.com.

    How to get the Piwik AOM plugin ?

    The Piwik AOM plugin is freely available through the Piwik marketplace at https://plugins.piwik.org/AOM

    Did you like this article ? If yes do not hesitate to share it or give your feedback about the topic you would like us to write about.

  • Recording a video using MediaRecorder

    21 juillet 2016, par Cédric Portmann

    I am currently using the TextureFromCameraActivity from Grafika to record a video in square ( 1:1 ) resolution. Therefor I the GLES20.glViewport so that the video gets moved to the top and it appears to be squared. Now I would like to record this square view using the MediaRecorder or at least record the camera with normal resolutiona and then crop it using FFmpeg. However I get the same error over and over again and I cant figure out why.

    The error I get :

    start called in an invalid state : 4

    And yes I added all the necessary permissions.

    android.permission.WRITE_EXTERNAL_STORAGE android.permission.CAMERA
    android.permission.RECORD_VIDEO android.permission.RECORD_AUDIO
    android.permission.STORAGE android.permission.READ_EXTERNAL_STORAGE

    Here the modified code :

    https://github.com/google/grafika

    Thanks for your help :D

    package com.android.grafika;

    import android.graphics.SurfaceTexture;
    import android.hardware.Camera;
    import android.media.CamcorderProfile;
    import android.media.MediaRecorder;
    import android.opengl.GLES20;
    import android.opengl.Matrix;
    import android.os.Bundle;
    import android.os.Environment;
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.Surface;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    import android.view.View;
    import android.widget.Button;
    import android.widget.SeekBar;
    import android.widget.TextView;
    import android.app.Activity;
    import android.widget.Toast;

    import com.android.grafika.gles.Drawable2d;
    import com.android.grafika.gles.EglCore;
    import com.android.grafika.gles.GlUtil;
    import com.android.grafika.gles.Sprite2d;
    import com.android.grafika.gles.Texture2dProgram;
    import com.android.grafika.gles.WindowSurface;

    import java.io.File;
    import java.io.IOException;
    import java.lang.ref.WeakReference;


    public class TextureFromCameraActivity extends Activity implements View.OnClickListener, SurfaceHolder.Callback,
           SeekBar.OnSeekBarChangeListener {


       private static final int DEFAULT_ZOOM_PERCENT = 0;      // 0-100
       private static final int DEFAULT_SIZE_PERCENT = 80;     // 0-100
       private static final int DEFAULT_ROTATE_PERCENT = 75;    // 0-100

       // Requested values; actual may differ.
       private static final int REQ_CAMERA_WIDTH = 720;
       private static final int REQ_CAMERA_HEIGHT = 720;
       private static final int REQ_CAMERA_FPS = 30;

       // The holder for our SurfaceView.  The Surface can outlive the Activity (e.g. when
       // the screen is turned off and back on with the power button).
       //
       // This becomes non-null after the surfaceCreated() callback is called, and gets set
       // to null when surfaceDestroyed() is called.
       private static SurfaceHolder sSurfaceHolder;

       // Thread that handles rendering and controls the camera.  Started in onResume(),
       // stopped in onPause().
       private RenderThread mRenderThread;

       // Receives messages from renderer thread.
       private MainHandler mHandler;

       // User controls.
       private SeekBar mZoomBar;
       private SeekBar mSizeBar;
       private SeekBar mRotateBar;

       // These values are passed to us by the camera/render thread, and displayed in the UI.
       // We could also just peek at the values in the RenderThread object, but we'd need to
       // synchronize access carefully.
       private int mCameraPreviewWidth, mCameraPreviewHeight;
       private float mCameraPreviewFps;
       private int mRectWidth, mRectHeight;
       private int mZoomWidth, mZoomHeight;
       private int mRotateDeg;
       SurfaceHolder sh;
       MediaRecorder recorder;
       SurfaceHolder holder;
       boolean recording = false;

       public static final String TAG = "VIDEOCAPTURE";

       private static final File OUTPUT_DIR = Environment.getExternalStorageDirectory();


       @Override
       protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);

           recorder = new MediaRecorder();



           setContentView(R.layout.activity_texture_from_camera);

           mHandler = new MainHandler(this);

           SurfaceView cameraView = (SurfaceView) findViewById(R.id.cameraOnTexture_surfaceView);
           sh = cameraView.getHolder();
           cameraView.setClickable(true);// make the surface view clickable
           sh.addCallback(this);


           //prepareRecorder();


           mZoomBar = (SeekBar) findViewById(R.id.tfcZoom_seekbar);
           mSizeBar = (SeekBar) findViewById(R.id.tfcSize_seekbar);
           mRotateBar = (SeekBar) findViewById(R.id.tfcRotate_seekbar);
           mZoomBar.setProgress(DEFAULT_ZOOM_PERCENT);
           mSizeBar.setProgress(DEFAULT_SIZE_PERCENT);
           mRotateBar.setProgress(DEFAULT_ROTATE_PERCENT);
           mZoomBar.setOnSeekBarChangeListener(this);
           mSizeBar.setOnSeekBarChangeListener(this);
           mRotateBar.setOnSeekBarChangeListener(this);

           Button record_btn = (Button)findViewById(R.id.button);
           record_btn.setOnClickListener(this);
           initRecorder();


           updateControls();




       }





       @Override
       protected void onResume() {
           Log.d(TAG, "onResume BEGIN");
           super.onResume();

           mRenderThread = new RenderThread(mHandler);
           mRenderThread.setName("TexFromCam Render");
           mRenderThread.start();
           mRenderThread.waitUntilReady();

           RenderHandler rh = mRenderThread.getHandler();
           rh.sendZoomValue(mZoomBar.getProgress());
           rh.sendSizeValue(mSizeBar.getProgress());
           rh.sendRotateValue(mRotateBar.getProgress());

           if (sSurfaceHolder != null) {
               Log.d(TAG, "Sending previous surface");
               rh.sendSurfaceAvailable(sSurfaceHolder, false);
           } else {
               Log.d(TAG, "No previous surface");
           }
           Log.d(TAG, "onResume END");
       }

       @Override
       protected void onPause() {
           Log.d(TAG, "onPause BEGIN");
           super.onPause();

           RenderHandler rh = mRenderThread.getHandler();
           rh.sendShutdown();
           try {
               mRenderThread.join();
           } catch (InterruptedException ie) {
               // not expected
               throw new RuntimeException("join was interrupted", ie);
           }
           mRenderThread = null;
           Log.d(TAG, "onPause END");
       }

       @Override   // SurfaceHolder.Callback
       public void surfaceCreated(SurfaceHolder holder) {
           Log.d(TAG, "surfaceCreated holder=" + holder + " (static=" + sSurfaceHolder + ")");
           if (sSurfaceHolder != null) {
               throw new RuntimeException("sSurfaceHolder is already set");
           }

           sSurfaceHolder = holder;

           if (mRenderThread != null) {
               // Normal case -- render thread is running, tell it about the new surface.
               RenderHandler rh = mRenderThread.getHandler();
               rh.sendSurfaceAvailable(holder, true);
           } else {
               // Sometimes see this on 4.4.x N5: power off, power on, unlock, with device in
               // landscape and a lock screen that requires portrait.  The surface-created
               // message is showing up after onPause().
               //
               // Chances are good that the surface will be destroyed before the activity is
               // unpaused, but we track it anyway.  If the activity is un-paused and we start
               // the RenderThread, the SurfaceHolder will be passed in right after the thread
               // is created.
               Log.d(TAG, "render thread not running");
           }

           recorder.setPreviewDisplay(holder.getSurface());

       }

       @Override   // SurfaceHolder.Callback
       public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
           Log.d(TAG, "surfaceChanged fmt=" + format + " size=" + width + "x" + height +
                   " holder=" + holder);

           if (mRenderThread != null) {
               RenderHandler rh = mRenderThread.getHandler();
               rh.sendSurfaceChanged(format, width, height);
           } else {
               Log.d(TAG, "Ignoring surfaceChanged");
               return;
           }
       }

       @Override   // SurfaceHolder.Callback
       public void surfaceDestroyed(SurfaceHolder holder) {
           // In theory we should tell the RenderThread that the surface has been destroyed.
           if (mRenderThread != null) {
               RenderHandler rh = mRenderThread.getHandler();
               rh.sendSurfaceDestroyed();
           }
           Log.d(TAG, "surfaceDestroyed holder=" + holder);
           sSurfaceHolder = null;
       }

       @Override   // SeekBar.OnSeekBarChangeListener
       public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
           if (mRenderThread == null) {
               // Could happen if we programmatically update the values after setting a listener
               // but before starting the thread.  Also, easy to cause this by scrubbing the seek
               // bar with one finger then tapping "recents" with another.
               Log.w(TAG, "Ignoring onProgressChanged received w/o RT running");
               return;
           }
           RenderHandler rh = mRenderThread.getHandler();

           // "progress" ranges from 0 to 100
           if (seekBar == mZoomBar) {
               //Log.v(TAG, "zoom: " + progress);
               rh.sendZoomValue(progress);
           } else if (seekBar == mSizeBar) {
               //Log.v(TAG, "size: " + progress);
               rh.sendSizeValue(progress);
           } else if (seekBar == mRotateBar) {
               //Log.v(TAG, "rotate: " + progress);
               rh.sendRotateValue(progress);
           } else {
               throw new RuntimeException("unknown seek bar");
           }

           // If we're getting preview frames quickly enough we don't really need this, but
           // we don't want to have chunky-looking resize movement if the camera is slow.
           // OTOH, if we get the updates too quickly (60fps camera?), this could jam us
           // up and cause us to run behind.  So use with caution.
           rh.sendRedraw();
       }

       @Override   // SeekBar.OnSeekBarChangeListener
       public void onStartTrackingTouch(SeekBar seekBar) {}
       @Override   // SeekBar.OnSeekBarChangeListener
       public void onStopTrackingTouch(SeekBar seekBar) {}
       @Override

       /**
        * Handles any touch events that aren't grabbed by one of the controls.
        */
       public boolean onTouchEvent(MotionEvent e) {
           float x = e.getX();
           float y = e.getY();

           switch (e.getAction()) {
               case MotionEvent.ACTION_MOVE:
               case MotionEvent.ACTION_DOWN:
                   //Log.v(TAG, "onTouchEvent act=" + e.getAction() + " x=" + x + " y=" + y);
                   if (mRenderThread != null) {
                       RenderHandler rh = mRenderThread.getHandler();
                       rh.sendPosition((int) x, (int) y);

                       // Forcing a redraw can cause sluggish-looking behavior if the touch
                       // events arrive quickly.
                       //rh.sendRedraw();
                   }
                   break;
               default:
                   break;
           }

           return true;
       }

       /**
        * Updates the current state of the controls.
        */
       private void updateControls() {
           String str = getString(R.string.tfcCameraParams, mCameraPreviewWidth,
                   mCameraPreviewHeight, mCameraPreviewFps);
           TextView tv = (TextView) findViewById(R.id.tfcCameraParams_text);
           tv.setText(str);

           str = getString(R.string.tfcRectSize, mRectWidth, mRectHeight);
           tv = (TextView) findViewById(R.id.tfcRectSize_text);
           tv.setText(str);

           str = getString(R.string.tfcZoomArea, mZoomWidth, mZoomHeight);
           tv = (TextView) findViewById(R.id.tfcZoomArea_text);
           tv.setText(str);
       }

       @Override
       public void onClick(View view) {

           if (recording) {
               recorder.stop();
               recording = false;

               // Let's initRecorder so we can record again
               initRecorder();
               prepareRecorder();
           } else {
               recording = true;
               recorder.start();
           }
       }


       private void initRecorder() {
           recorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
           recorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT);

           CamcorderProfile cpHigh = CamcorderProfile
                   .get(CamcorderProfile.QUALITY_HIGH);
           recorder.setProfile(cpHigh);
           String path = Environment.getExternalStorageDirectory() + File.separator
                   + Environment.DIRECTORY_DCIM + File.separator + "AlphaRun";

           recorder.setOutputFile(path);
           recorder.setMaxDuration(50000); // 50 seconds
           recorder.setMaxFileSize(5000000); // Approximately 5 megabytes

       }

       private void prepareRecorder() {


           try {
               recorder.prepare();
           } catch (IllegalStateException e) {
               e.printStackTrace();
               finish();
           } catch (IOException e) {
               e.printStackTrace();
               finish();
           }
       }




       /**
        * Thread that handles all rendering and camera operations.
        */
       private static class RenderThread extends Thread implements
               SurfaceTexture.OnFrameAvailableListener {
           // Object must be created on render thread to get correct Looper, but is used from
           // UI thread, so we need to declare it volatile to ensure the UI thread sees a fully
           // constructed object.
           private volatile RenderHandler mHandler;

           // Used to wait for the thread to start.
           private Object mStartLock = new Object();
           private boolean mReady = false;

           private MainHandler mMainHandler;

           private Camera mCamera;
           private int mCameraPreviewWidth, mCameraPreviewHeight;

           private EglCore mEglCore;
           private WindowSurface mWindowSurface;
           private int mWindowSurfaceWidth;
           private int mWindowSurfaceHeight;

           // Receives the output from the camera preview.
           private SurfaceTexture mCameraTexture;

           // Orthographic projection matrix.
           private float[] mDisplayProjectionMatrix = new float[16];

           private Texture2dProgram mTexProgram;
           private final ScaledDrawable2d mRectDrawable =
                   new ScaledDrawable2d(Drawable2d.Prefab.RECTANGLE);
           private final Sprite2d mRect = new Sprite2d(mRectDrawable);

           private int mZoomPercent = DEFAULT_ZOOM_PERCENT;
           private int mSizePercent = DEFAULT_SIZE_PERCENT;
           private int mRotatePercent = DEFAULT_ROTATE_PERCENT;
           private float mPosX, mPosY;


           /**
            * Constructor.  Pass in the MainHandler, which allows us to send stuff back to the
            * Activity.
            */
           public RenderThread(MainHandler handler) {
               mMainHandler = handler;

           }

           /**
            * Thread entry point.
            */
           @Override
           public void run() {
               Looper.prepare();

               // We need to create the Handler before reporting ready.
               mHandler = new RenderHandler(this);
               synchronized (mStartLock) {
                   mReady = true;
                   mStartLock.notify();    // signal waitUntilReady()
               }

               // Prepare EGL and open the camera before we start handling messages.
               mEglCore = new EglCore(null, 0);
               openCamera(REQ_CAMERA_WIDTH, REQ_CAMERA_HEIGHT, REQ_CAMERA_FPS);

               Looper.loop();

               Log.d(TAG, "looper quit");
               releaseCamera();
               releaseGl();
               mEglCore.release();

               synchronized (mStartLock) {
                   mReady = false;
               }
           }

           /**
            * Waits until the render thread is ready to receive messages.
            * <p>
            * Call from the UI thread.
            */
           public void waitUntilReady() {
               synchronized (mStartLock) {
                   while (!mReady) {
                       try {
                           mStartLock.wait();
                       } catch (InterruptedException ie) { /* not expected */ }
                   }
               }
           }

           /**
            * Shuts everything down.
            */
           private void shutdown() {
               Log.d(TAG, "shutdown");
               Looper.myLooper().quit();
           }

           /**
            * Returns the render thread's Handler.  This may be called from any thread.
            */
           public RenderHandler getHandler() {
               return mHandler;
           }

           /**
            * Handles the surface-created callback from SurfaceView.  Prepares GLES and the Surface.
            */
           private void surfaceAvailable(SurfaceHolder holder, boolean newSurface) {

               Surface surface = holder.getSurface();
               mWindowSurface = new WindowSurface(mEglCore, surface, false);
               mWindowSurface.makeCurrent();

               // Create and configure the SurfaceTexture, which will receive frames from the
               // camera.  We set the textured rect's program to render from it.
               mTexProgram = new Texture2dProgram(Texture2dProgram.ProgramType.TEXTURE_EXT);
               int textureId = mTexProgram.createTextureObject();
               mCameraTexture = new SurfaceTexture(textureId);
               mRect.setTexture(textureId);

               if (!newSurface) {
                   // This Surface was established on a previous run, so no surfaceChanged()
                   // message is forthcoming.  Finish the surface setup now.
                   //
                   // We could also just call this unconditionally, and perhaps do an unnecessary
                   // bit of reallocating if a surface-changed message arrives.
                   mWindowSurfaceWidth = mWindowSurface.getWidth();
                   mWindowSurfaceHeight = mWindowSurface.getWidth();
                   finishSurfaceSetup();
               }

               mCameraTexture.setOnFrameAvailableListener(this);



           }

           /**
            * Releases most of the GL resources we currently hold (anything allocated by
            * surfaceAvailable()).
            * </p><p>
            * Does not release EglCore.
            */
           private void releaseGl() {
               GlUtil.checkGlError("releaseGl start");

               if (mWindowSurface != null) {
                   mWindowSurface.release();
                   mWindowSurface = null;
               }
               if (mTexProgram != null) {
                   mTexProgram.release();
                   mTexProgram = null;
               }
               GlUtil.checkGlError("releaseGl done");

               mEglCore.makeNothingCurrent();
           }

           /**
            * Handles the surfaceChanged message.
            * </p><p>
            * We always receive surfaceChanged() after surfaceCreated(), but surfaceAvailable()
            * could also be called with a Surface created on a previous run.  So this may not
            * be called.
            */
           private void surfaceChanged(int width, int height) {
               Log.d(TAG, "RenderThread surfaceChanged " + width + "x" + height);

               mWindowSurfaceWidth = width;
               mWindowSurfaceHeight = width;
               finishSurfaceSetup();
           }

           /**
            * Handles the surfaceDestroyed message.
            */
           private void surfaceDestroyed() {
               // In practice this never appears to be called -- the activity is always paused
               // before the surface is destroyed.  In theory it could be called though.
               Log.d(TAG, "RenderThread surfaceDestroyed");
               releaseGl();
           }

           /**
            * Sets up anything that depends on the window size.
            * </p><p>
            * Open the camera (to set mCameraAspectRatio) before calling here.
            */
           private void finishSurfaceSetup() {
               int width = mWindowSurfaceWidth;
               int height = mWindowSurfaceHeight;
               Log.d(TAG, "finishSurfaceSetup size=" + width + "x" + height +
                       " camera=" + mCameraPreviewWidth + "x" + mCameraPreviewHeight);

               // Use full window.
               GLES20.glViewport(0, 700, width, height);

               // Simple orthographic projection, with (0,0) in lower-left corner.
               Matrix.orthoM(mDisplayProjectionMatrix, 0, 0, width, 0, height, -1, 1);

               // Default position is center of screen.
               mPosX = width / 2.0f;
               mPosY = height / 2.0f;

               updateGeometry();

               // Ready to go, start the camera.
               Log.d(TAG, "starting camera preview");
               try {
                   mCamera.setPreviewTexture(mCameraTexture);

               } catch (IOException ioe) {
                   throw new RuntimeException(ioe);
               }
               mCamera.startPreview();
           }

           /**
            * Updates the geometry of mRect, based on the size of the window and the current
            * values set by the UI.
            */
           private void updateGeometry() {
               int width = mWindowSurfaceWidth;
               int height = mWindowSurfaceHeight;


               int smallDim = Math.min(width, height);
               // Max scale is a bit larger than the screen, so we can show over-size.
               float scaled = smallDim * (mSizePercent / 100.0f) * 1.25f;
               float cameraAspect = (float) mCameraPreviewWidth / mCameraPreviewHeight;
               int newWidth = Math.round(scaled * cameraAspect);
               int newHeight = Math.round(scaled);

               float zoomFactor = 1.0f - (mZoomPercent / 100.0f);
               int rotAngle = Math.round(360 * (mRotatePercent / 100.0f));

               mRect.setScale(newWidth, newHeight);
               mRect.setPosition(mPosX, mPosY);
               mRect.setRotation(rotAngle);
               mRectDrawable.setScale(zoomFactor);

               mMainHandler.sendRectSize(newWidth, newHeight);
               mMainHandler.sendZoomArea(Math.round(mCameraPreviewWidth * zoomFactor),
                       Math.round(mCameraPreviewHeight * zoomFactor));
               mMainHandler.sendRotateDeg(rotAngle);
           }

           @Override   // SurfaceTexture.OnFrameAvailableListener; runs on arbitrary thread
           public void onFrameAvailable(SurfaceTexture surfaceTexture) {
               mHandler.sendFrameAvailable();
           }

           /**
            * Handles incoming frame of data from the camera.
            */
           private void frameAvailable() {
               mCameraTexture.updateTexImage();

               draw();
           }

           /**
            * Draws the scene and submits the buffer.
            */
           private void draw() {
               GlUtil.checkGlError("draw start");

               GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
               GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
               mRect.draw(mTexProgram, mDisplayProjectionMatrix);
               mWindowSurface.swapBuffers();

               GlUtil.checkGlError("draw done");
           }

           /**
            * Opens a camera, and attempts to establish preview mode at the specified width
            * and height with a fixed frame rate.
            * </p><p>
            * Sets mCameraPreviewWidth / mCameraPreviewHeight.
            */
           private void openCamera(int desiredWidth, int desiredHeight, int desiredFps) {
               if (mCamera != null) {
                   throw new RuntimeException("camera already initialized");
               }

               Camera.CameraInfo info = new Camera.CameraInfo();

               // Try to find a front-facing camera (e.g. for videoconferencing).
               int numCameras = Camera.getNumberOfCameras();
               for (int i = 0; i &lt; numCameras; i++) {
                   Camera.getCameraInfo(i, info);
                   if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                       mCamera = Camera.open(i);
                       break;
                   }
               }
               if (mCamera == null) {
                   Log.d(TAG, "No front-facing camera found; opening default");
                   mCamera = Camera.open();    // opens first back-facing camera
               }
               if (mCamera == null) {
                   throw new RuntimeException("Unable to open camera");
               }

               Camera.Parameters parms = mCamera.getParameters();

               CameraUtils.choosePreviewSize(parms, desiredWidth, desiredHeight);
               parms.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
               // Try to set the frame rate to a constant value.
               int thousandFps = CameraUtils.chooseFixedPreviewFps(parms, desiredFps * 1000);

               // Give the camera a hint that we're recording video.  This can have a big
               // impact on frame rate.
               parms.setRecordingHint(true);

               mCamera.setParameters(parms);

               int[] fpsRange = new int[2];
               Camera.Size mCameraPreviewSize = parms.getPreviewSize();
               parms.getPreviewFpsRange(fpsRange);
               String previewFacts = mCameraPreviewSize.width + "x" + mCameraPreviewSize.height;
               if (fpsRange[0] == fpsRange[1]) {
                   previewFacts += " @" + (fpsRange[0] / 1000.0) + "fps";
               } else {
                   previewFacts += " @[" + (fpsRange[0] / 1000.0) +
                           " - " + (fpsRange[1] / 1000.0) + "] fps";
               }
               Log.i(TAG, "Camera config: " + previewFacts);

               mCameraPreviewWidth = mCameraPreviewSize.width;
               mCameraPreviewHeight = mCameraPreviewSize.height;
               mMainHandler.sendCameraParams(mCameraPreviewWidth, mCameraPreviewHeight,
                       thousandFps / 1000.0f);
           }

           /**
            * Stops camera preview, and releases the camera to the system.
            */
           private void releaseCamera() {
               if (mCamera != null) {
                   mCamera.stopPreview();
                   mCamera.release();
                   mCamera = null;
                   Log.d(TAG, "releaseCamera -- done");
               }
           }
       }

    }
    </p>
  • Introducing Crash Analytics for Matomo

    30 août 2023, par Erin — Community, Plugins

    Bugs and development go hand in hand. As code matures, it contends with new browser iterations, clashes with ad blockers and other software quirks, resulting in the inevitable emergence of bugs. In fact, a staggering 13% of all pageviews come with lurking JavaScript errors.

    Monitoring for crashes becomes an unrelenting task. Amidst this never-ending effort to remove bugs, a SurveyMonkey study unveils a shared reality : a resounding 66% of individuals have encountered bug-ridden websites.

    These bugs lead to problems like malfunctioning shopping carts, glitchy checkout procedures and contact forms that just won’t cooperate. But they’re not just minor annoyances – they pose a real danger to your conversion rates and revenue.

    According to a study, 58% of visitors are inclined to abandon purchases as a result of bugs, while an astonishing 75% are driven to completely abandon websites due to these frustrating experiences.

    Imagine a website earning approximately 25,000 EUR per month. Now, factor in errors occurring in 13% of all pageviews. The result ? A potential monthly loss of 1,885 EUR.

    Meet Crash Analytics

    Driven by our vision to create an empowering analytics product, we’re excited to introduce Crash Analytics, an innovative plugin for Matomo On-Premise that automatically tracks bugs on your website.

    Crash Analytics for Matomo Evolution Graph
    View crash reports by evolution over time

    By offering insights into the precise bug location and the user’s interactions that triggered it, along with details about their device type, browser and more, Crash Analytics empowers you to swiftly address crashes, leading to an improved user experience, higher conversion rates and revenue growth.

    Soon, Crash Analytics will become available to Matomo Cloud users as well, so stay tuned for further updates and announcements.

    Say goodbye to lost revenue – never miss a bug again

    Even if you put your website through the toughest tests, it’s hard to predict every little hiccup that can pop up across different browsers, setups and situations. Factors such as ad blockers, varying internet speeds for visitors and browser updates can add an extra layer of complexity.

    When these crashes happen, you want to know immediately. However, according to a study, only 29% of surveyed respondents would report the existence of the site bug to the website operator. These bugs that go unnoticed can really hurt your bottom line and conversion rates, causing you to lose out on revenue and leaving your users frustrated and disappointed.

    Crash detail report in Crash Analytics for Matomo
    Detailed crash report

    Crash Analytics is here to bridge this gap. Armed with scheduled reporting (via email or texts) and automated alert functionalities, you gain the power to instantly detect bugs as they occur on your site. This proactive approach ensures that even the subtlest of issues are brought to your attention promptly. 

    With automated reports and alerts, you can also opt to receive notifications when crashes increase or ignore specific crashes that you deem insignificant. This keeps you in the loop with only the issues that truly matter, helping you cut out the noise and take immediate action.

    Forward crash data

    Easily forward crash data to developers and synchronise the efforts of technical teams and marketing experts. Track emerging, disappearing and recurring errors, ensuring that crash data is efficiently relayed to developers to prioritise fixes that matter.

    Eemerging, disappearing and recurring crashes in Crash Analytics for Matomo
    Track emerging, disappearing and recurring bugs

    Plus, your finger is always on the pulse with real-time reports that offer a live view of crashes happening at the moment, an especially helpful feature after deploying changes. Use annotations to mark deploys and correlate them with crash data, enabling you to quickly identify if a new bug is linked to recent updates or modifications.

    Crash data in real time
    Crash data in real time

    And with our mobile app, you can effortlessly stay connected to your website’s performance, conveniently accessing crash information anytime and anywhere. This ensures you’re in complete control of your site’s health, even when you’re on the move.

    Streamline bug resolution with combined web and crash analytics

    Crash Analytics for Matomo doesn’t just stop at pinpointing bug locations ; it goes a step further by providing you with a holistic perspective of user interactions. Seamlessly combining Matomo’s traditional and behavioural web analytics features—like segments, session recordings and visitor logs—with crash data, this integrated approach unveils a wealth of insights so you can quickly resolve bugs. 

    For instance, let’s say a user encounters a bug while attempting to complete a purchase on your e-commerce website. Crash Analytics reveals the exact point of failure, but to truly grasp the situation, you delve into the session recordings. These recordings offer a front-row seat to the user’s journey—every click and interaction that led to the bug. Session recordings are especially helpful when you are struggling to reproduce an issue.

    Visits log combined with crash data in Matomo
    Visits log overlayed with crash data

    Additionally, the combination of visitor logs with crash data offers a comprehensive timeline of a user’s engagement. This helps you understand their activity leading up to the bug, such as pages visited, actions taken and devices used. Armed with these multifaceted insights, you can confidently pinpoint the root causes and address the crash immediately.

    With segments, you have the ability to dissect the data and compare experiences among distinct user groups. For example, you can compare mobile visitors to desktop visitors to determine if the issue is isolated or widespread and what impact the issue is having on the user experience of different user groups. 

    The combination of crash data with Matomo’s comprehensive web analytics equips you with the tools needed to elevate user experiences and ultimately drive revenue growth.

    Start in seconds, shape as needed : Your path to a 100% reliable website

    Crash Analytics makes the path to a reliable website simple. You don’t have to deal with intricate setups—crash detection starts without any configuration. 

    Plus, Crash Analytics excels in cross-stack proficiency, seamlessly extending its capabilities beyond automatically tracking JavaScript errors to covering server-side crashes as well, whether they occur in PHP, Android, iOS, Java or other frameworks. This versatile approach ensures that Crash Analytics comprehensively supports your website’s health and performance across various technological landscapes.

    Elevate your website with Crash Analytics

    Experience the seamless convergence of bug tracking and web analytics, allowing you to delve into user interactions, session recordings and visitor logs. With the flexibility of customising real-time alerts and scheduled reports, alongside cross-stack proficiency, Crash Analytics becomes your trusted ally in enhancing your website’s reliability and user satisfaction to increase conversions and drive revenue growth. Equip yourself to swiftly address issues and create a website where user experiences take precedence.

    Start your 30-day free trial of our Crash Analytics plugin today, and stay tuned for its availability on Matomo Cloud.